Skip to content

Commit

Permalink
Make BaseTask easier to use (#435)
Browse files Browse the repository at this point in the history
* Make BaseTask easier to use

* Remove accidentally added file

* No need to provide a dummy implementation for an abstract method
  • Loading branch information
mdickinson committed Jul 19, 2021
1 parent 60cf170 commit b825c2b
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 47 deletions.
23 changes: 17 additions & 6 deletions docs/source/guide/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
Advanced topics
===============

.. note::
Support for writing custom tasks is provisional. The API is subject to
change in future releases. Feedback on the feature is welcome.


Creating your own background task type
--------------------------------------

Expand Down Expand Up @@ -77,14 +82,17 @@ isn't strictly necessary, but it makes the code cleaner.
The background callable
~~~~~~~~~~~~~~~~~~~~~~~

Next, we define the callable that will be run in the background. This callable
must accept two arguments (which will be passed by position): ``send`` and
Next, we define the callable that will be run in the background. The callable
itself expects two arguments (which will be passed by position): ``send`` and
``cancelled``. The ``send`` object is a callable which will be used to send
messages to the foreground. The ``cancelled`` object is a zero-argument
callable which can be used to check for cancellation requests. For convenience,
we inherit from |BaseTask|, which takes care of sending standard messages
to the future letting the future know that the background task has started,
stopped, or raised an exception.
callable which can be used to check for cancellation requests.

However, instead of implementing this callable directly, we inherit from the
|BaseTask| abstract base class. This requires us to implement a (parameterless)
|run| method for the body of the task. The |run| method has access to methods
|send| and |cancelled| to send messages to the associated |BaseFuture| instance
and to check whether the user has requested cancellation.

Here's the ``fizz_buzz`` callable.

Expand Down Expand Up @@ -180,12 +188,15 @@ of the new background task type:
.. |BaseFuture| replace:: :class:`~.BaseFuture`
.. |BaseTask| replace:: :class:`~.BaseTask`
.. |cancelled| replace:: :meth:`~.BaseTask.cancelled`
.. |dispatch| replace:: :meth:`~.BaseFuture.dispatch`
.. |exception| replace:: :attr:`~traits_futures.base_future.BaseFuture.exception`
.. |HasStrictTraits| replace:: :class:`~traits.has_traits.HasStrictTraits`
.. |IFuture| replace:: :class:`~.IFuture`
.. |ITaskSpecification| replace:: :class:`~.ITaskSpecification`
.. |result| replace:: :attr:`~traits_futures.base_future.BaseFuture.result`
.. |run| replace:: :meth:`~.BaseTask.run`
.. |send| replace:: :meth:`~.BaseTask.send`
.. |submit| replace:: :meth:`~.submit`
.. |submit_call| replace:: :func:`~.submit_call`
.. |submit_iteration| replace:: :func:`~.submit_iteration`
Expand Down
10 changes: 5 additions & 5 deletions docs/source/guide/examples/fizz_buzz_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@ class FizzBuzzTask(BaseTask):
returns ``True`` if cancellation has been requested, and ``False``
otherwise.
"""
def run(self, send, cancelled):
def run(self):
n = 1
while not cancelled():
while not self.cancelled():

n_is_multiple_of_3 = n % 3 == 0
n_is_multiple_of_5 = n % 5 == 0

if n_is_multiple_of_3 and n_is_multiple_of_5:
send((FIZZ_BUZZ, n))
self.send(FIZZ_BUZZ, n)
elif n_is_multiple_of_3:
send((FIZZ, n))
self.send(FIZZ, n)
elif n_is_multiple_of_5:
send((BUZZ, n))
self.send(BUZZ, n)

time.sleep(1.0)
n += 1
Expand Down
2 changes: 1 addition & 1 deletion traits_futures/background_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, callable, args, kwargs):
self.args = args
self.kwargs = kwargs

def run(self, send, cancelled):
def run(self):
return self.callable(*self.args, **self.kwargs)


Expand Down
6 changes: 3 additions & 3 deletions traits_futures/background_iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ def __init__(self, callable, args, kwargs):
self.args = args
self.kwargs = kwargs

def run(self, send, cancelled):
def run(self):
iterable = iter(self.callable(*self.args, **self.kwargs))

while True:
if cancelled():
if self.cancelled():
return None

try:
Expand All @@ -46,7 +46,7 @@ def run(self, send, cancelled):
# exception carries that value. Return it.
return e.value

send((GENERATED, result))
self.send(GENERATED, result)
# Don't keep a reference around until the next iteration.
del result

Expand Down
6 changes: 3 additions & 3 deletions traits_futures/background_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def report(self, progress_info):
"""
if self.cancelled():
raise ProgressCancelled("Task was cancelled")
self.send((PROGRESS, progress_info))
self.send(PROGRESS, progress_info)


class ProgressTask(BaseTask):
Expand All @@ -86,8 +86,8 @@ def __init__(self, callable, args, kwargs):
self.args = args
self.kwargs = kwargs

def run(self, send, cancelled):
progress = ProgressReporter(send=send, cancelled=cancelled)
def run(self):
progress = ProgressReporter(send=self.send, cancelled=self.cancelled)
try:
return self.callable(
*self.args,
Expand Down
76 changes: 47 additions & 29 deletions traits_futures/base_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Base class providing common pieces of the Future machinery.
"""

import abc
import logging

from traits.api import (
Expand Down Expand Up @@ -501,7 +502,7 @@ def _update_property_traits(self, event):
self.trait_property_changed("done", old_done, new_done)


class BaseTask:
class BaseTask(abc.ABC):
"""
Mixin for background task classes, making those classes callable.
Expand All @@ -515,43 +516,60 @@ class BaseTask:
for execution of the background task and sending of any custom messages.
"""

def run(self, send, cancelled):
@abc.abstractmethod
def run(self):
"""
Run the body of the background task.
Parameters
----------
send
single-argument callable used to send a message to the
associated future. It takes the message to be sent, and returns
no useful value.
cancelled
zero-argument callable that can be used to check whether
cancellation has been requested for this task. Returns ``True``
if cancellation has been requested, else ``False``.
Returns
-------
any : object
May return any object. That object will be delivered to the
future's ``result`` attribute.
"""
raise NotImplementedError(
"This method should be implemented by subclasses."
)

def __call__(self, send, cancelled):
if cancelled():
send((ABANDONED, None))
return
def send(self, message_type, message_arg=None):
"""
Send a message to the associated future.
Parameters
----------
message_type : str
The type of the message. This is used by ``BaseFuture`` when
dispatching messages to appropriate handlers.
message_arg : object, optional
Message argument, if any. If not given, ``None`` is used.
"""
self.__send((SENT, (message_type, message_arg)))

send((STARTED, None))
def cancelled(self):
"""
Determine whether the user has requested cancellation.
Returns True if the user has requested cancellation via the associated
future's ``cancel`` method, and False otherwise.
Returns
-------
bool
"""
return self.__cancelled()

def __call__(self, send, cancelled):
self.__send = send
self.__cancelled = cancelled
try:
result = self.run(
lambda message: send((SENT, message)),
cancelled,
)
except BaseException as e:
send((RAISED, marshal_exception(e)))
else:
send((RETURNED, result))
if cancelled():
send((ABANDONED, None))
return

send((STARTED, None))
try:
result = self.run()
except BaseException as e:
send((RAISED, marshal_exception(e)))
else:
send((RETURNED, result))
finally:
del self.__cancelled
del self.__send

0 comments on commit b825c2b

Please sign in to comment.