[Twisted-Python] Sequential use of asynchronous calls

Ed Suominen general at eepatents.com
Sat May 26 11:18:28 EDT 2007


http://foss.eepatents.com/api/twisted-goodies/taskqueue.base.TaskQueue.html

Maarten ter Huurne wrote:
> Hi,
> 
> Sometimes I want to use several asynchronous calls in a fixed sequence. For 
> example, a web application might:
> - authenticate the user
> - fetch info from a database
> - present the result
> 
> Implementing this using Deferreds and separate callback+errback functions has 
> the disadvantage that the sequence itself is not easy to recognise anymore, 
> as it gets spread out over multiple functions.
> 
> So I got creative with the new generator features of Python 2.5 and came up 
> with a decorator named "sequential", which can be applied to generator 
> functions. It consumes Deferreds that are yielded by the generator and sends 
> back the result when it becomes available, or raises an Exception in the 
> generator if the deferred action fails.
> 
> The decorated function returns a Deferred itself, which is fired upon 
> completion of the sequence. In particular, this allows nesting sequences 
> inside sequences.
> 
> This is an example of a program using it, it is an elaborated version of the 
> first example from the Deferred Reference:
> 
> ===
> from twisted.internet import defer, reactor
> from twisted.python import log
> 
> from sequential import sequential
> 
> def getDummyData(x):
>     d = defer.Deferred()
>     if x < 0:
>         reactor.callLater(1, d.errback, ValueError('negative value: %d' % x))
>     else:
>         reactor.callLater(1, d.callback, x * 3)
>     return d
> 
> @sequential
> def work():
>     print (yield getDummyData(3))
>     print (yield getDummyData(4))
>     print (yield 'immediate')
>     print (yield getDummyData(6))
>     try:
>         print (yield getDummyData(-7))
>     except ValueError, e:
>         print 'failed:', e
> 
> @sequential
> def main(message):
>     print message, 'once...'
>     yield work()
>     print message, 'twice...'
>     yield work()
> 
> def done(result):
>     reactor.stop()
> 
> def failed(fail):
>     log.err(fail)
>     reactor.stop()
> 
> d = main('going')
> d.addCallback(done)
> d.addErrback(failed)
> 
> reactor.run()
> ===
> 
> And here is the implementation of the "sequential" module:
> 
> ===
> from twisted.internet import defer
> from twisted.python import failure
> 
> from functools import wraps
> from compiler.consts import CO_GENERATOR
> 
> class _SequentialCaller(object):
>     '''Repeatedly reads a Deferred from a generator and feeds it back the 
>     result when it becomes available.
>     '''
> 
>     def __init__(self, gen):
>         self.gen = gen
>         self.deferred = defer.Deferred()
> 
>     def start(self):
>         self.next(None)
>         return self.deferred
> 
>     def next(self, result):
>         while True:
>             try:
>                 if isinstance(result, failure.Failure):
>                     traceback = result.getTracebackObject() \
>                         if hasattr(result, 'getTracebackObject') else None
>                     d = self.gen.throw(
>                         result.type, result.getErrorMessage(), traceback
>                         )
>                 else:
>                     d = self.gen.send(result)
>             except StopIteration:
>                 self.deferred.callback(None)
>                 return
>             except StandardError:
>                 self.deferred.errback(failure.Failure())
>                 return
>             if isinstance(d, defer.Deferred):
>                 d.addCallback(lambda result: self.next(result))
>                 d.addErrback(lambda fail: self.next(fail))
>                 return
>             else:
>                 # Allow non-deferred values as well: for some Twisted calls,
>                 # you don't know whether the result will be deferred or not.
>                 result = d
> 
> def sequential(f):
>     if not (f.func_code.co_flags & CO_GENERATOR):
>         raise TypeError('function "%s" is not a generator' % f.__name__)
>     @wraps(f)
>     def wrapper(*args, **kvArgs):
>         return _SequentialCaller(f(*args, **kvArgs)).start()
>     return wrapper
> ===
> 
> I'd like some feedback on this:
> - would you consider this useful?
> - is the interface right or can it be improved?
> - is the implementation correct? (the example scenario doesn't test the error 
> path extensively, so there might be problems there)
> - is the use of Failure.getTracebackObject correct? (the version of Twisted 
> installed on my machine does not have it yet, I only read about it in the 
> sources on the API documentation site)
> - the "compiler.consts" module is not documented in the Python Library 
> Reference, does that mean it should not be used or did they forget to 
> document it?
> - anything else you'd like to say about it
> 
> Is there already something like this in Twisted or one of the toolkits built 
> on Twisted? I took a quick look at the "flow" modules, but that seems like a 
> more generic and flexible, but also more complex, approach.
> 
> If it would be a useful addition to Twisted or a Twisted-based toolkit, I'm 
> willing to improve the documentation and write test cases.
> 
> Bye,
> 		Maarten
> 
> 
> ------------------------------------------------------------------------
> 
> _______________________________________________
> Twisted-Python mailing list
> Twisted-Python at twistedmatrix.com
> http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python




More information about the Twisted-Python mailing list