Skip to content

Commit

Permalink
feat: add feeluown.task module
Browse files Browse the repository at this point in the history
  • Loading branch information
cosven committed Oct 22, 2019
1 parent ef064fd commit 9c8b5b2
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ LINT_FILES = fuocore/
LINT_FILES += feeluown/entry_points
LINT_FILES += feeluown/app.py
LINT_FILES += feeluown/ui.py
LINT_FILES += feeluown/task.py
LINT_FILES += feeluown/widgets/*_panel.py
LINT_FILES += feeluown/widgets/table_container.py
LINT_FILES += feeluown/widgets/songs_table.py
Expand Down
3 changes: 3 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,9 @@ metadata 是 ``AudioMeta`` 的实例。对于视频文件,metadata 则是 ``Vi
通用管理模块
----------------

.. automodule:: feeluown.task
:members:

.. automodule:: feeluown.version

.. automodule:: feeluown.tips
Expand Down
101 changes: 101 additions & 0 deletions feeluown/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import logging
from enum import Enum

logger = logging.getLogger(__name__)


class TaskKind(Enum):
preemptive = 'preemptive' #: preemptive task
cooperative = 'cooperative' #: cooperative task


class PreemptiveTaskSpec:
"""Preemptive task specification"""

def __init__(self, mgr, name):
"""
:param mgr: :class:`TaskManager`
:param name: task unique name
"""
self._mgr = mgr
self.name = name
self.kind = TaskKind.preemptive
self._task = None

def _before_bind(self):
if self._task is None:
return
self._task.cancel()
logger.info('preemptive-task(%s): try to cancel previous', self.name)
self._task = None

def bind_coro(self, coro):
"""run the coroutine and bind the task
it will cancel the previous task if exists
:return: :class:`asyncio.Task`
"""
self._before_bind()
self._task = self._mgr.loop.create_task(coro)
return self._task

def bind_blocking_io(self, func, *args):
"""run blocking io func in a thread executor, and bind the task
it will cancel the previous task if exists
:return: :class:`asyncio.Task`
"""
self._before_bind()
self._task = self._mgr.loop.run_in_executor(None, func, *args)
return self._task


class TaskManager:
"""named task manager
Usage::
async def fetch_song():
pass
task_name = 'unique-name'
task_spec = task_mgr.get_or_create(task_name, TaskType.preemptive)
task = task_spec.bind_coro(fetch_song())
"""
def __init__(self, app, loop):
"""
:param app: feeluown app instance
:param loop: asyncio event loop
"""
self._app = app

# only accessible for task instance
self.loop = loop

# store the name:taskspec mapping
self._store = {}

def get_or_create(self, name, kind=TaskKind.preemptive):
"""get task spec, it will be created if not exists
:param name: task identifier(name)
:param kind: :class:`TaskKind`
TODO: client should register first, then get by name
"""
if name not in self._store:
task_spec = self._create(name, kind)
else:
task_spec = self._store[name]
return task_spec

def _create(self, name, kind):
kind = TaskKind(kind)
task_spec = PreemptiveTaskSpec(self, name)
self._store[name] = task_spec
return task_spec
44 changes: 44 additions & 0 deletions tests/feeluown/test_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import asyncio
import time
from unittest import mock

import pytest

from feeluown.task import TaskManager, PreemptiveTaskSpec


@pytest.mark.asyncio
async def test_task_manager(app_mock):
loop = asyncio.get_event_loop()
task_mgr = TaskManager(app_mock, loop)
task_spec = task_mgr.get_or_create('fetch-song-standby')

async def fetch_song():
pass

mock_done_cb = mock.MagicMock()
task = task_spec.bind_coro(fetch_song())
task.add_done_callback(mock_done_cb)
await asyncio.sleep(0.1) # let task run
assert mock_done_cb.called is True


@pytest.mark.asyncio
async def test_preemptive_task_spec_bind_coro():
mgr = mock.MagicMock()
loop = asyncio.get_event_loop()
mgr.loop = loop
task_spec = PreemptiveTaskSpec(mgr, 'fetch-song-standby')

mock_cancelled_cb = mock.MagicMock()

async def fetch_song():
try:
await asyncio.sleep(0.1)
except asyncio.CancelledError:
mock_cancelled_cb()

task_spec.bind_coro(fetch_song())
await asyncio.sleep(0.1) # let fetch_song run
await task_spec.bind_coro(fetch_song())
assert mock_cancelled_cb.called is True

0 comments on commit 9c8b5b2

Please sign in to comment.