Archive for June, 2013

Yet another cancelable Twisted deferred class

Thursday, June 20th, 2013

I’m posting this (completely untested!) to illustrate how one could write a class to provide a Twisted deferred-like class, identical to the twisted.internet.defer.Deferred class, but which lets you call callback, errback, or cancel on the instance yourself. Hopefully that will make some sense. If not, let me know in the comments and I’ll try to be clearer. There’s also discussion of this issue (again) in the Twisted mailing list right now. For context, the discussion thread from January 2010 where I first wrote a class like the below is here.

from twisted.internet.defer import CancelledError, Deferred
from twisted.python.failure import Failure

class ControllableDeferred2013(object):

    """A Deferred-like class that takes a regular Twisted Deferred and
    provides a deferred that can be fired at will. If you have a regular
    Twisted Deferred, you can produce a deferred you have more control over
    by using your Deferred instance to make an instance of this class.

    Any time you need to fire a ControllableDeferred2013 instance for any
    reason, call its callback, errback or cancel method. It will fire
    immediately with the value you provide and the original Deferred will
    be cancelled."""

    def __init__(self, originalDeferred):
        self._fired = False
        self._originalDeferred = originalDeferred
        self._newDeferred = Deferred()
        for method in ('addBoth', 'addCallback', 'addCallbacks', 'addErrback',
                       'chainDeferred'):
            setattr(self, method, getattr(self._newDeferred, method))
        originalDeferred.addBoth(self._originalFired)

    def _originalFired(self, result):
        if not self._fired:
            self._fired = True
            self._originalDeferred.chainDeferred(self._newDeferred)

    def cancel(self, value=None):
        if not self._fired:
            self._fired = True
            self._newDeferred.errback(Failure(CancelledError(value)))
            self._originalDeferred.cancel()

    def callback(self, result=None):
        if not self._fired:
            self._fired = True
            self._newDeferred.callback(result)
            self._originalDeferred.cancel()

    def errback(self, fail=None):
        if not self._fired:
            self._fired = True
            self._newDeferred.errback(fail)
            self._originalDeferred.cancel()

    def pause(self):
        self._newDeferred.pause()
        self._originalDeferred.pause()

    def unpause(self):
        self._newDeferred.unpause()
        self._originalDeferred.unpause()