
Steve, I'm all too familar with the situation you describe, and I've come up with a pattern that works well for me. Here's how I would have written your example: class XMLRPCResponseClass(): def step1(self): finished = defer.Deferred() self.db.runQuery(blah).addCallback( self.step2, finished).addErrback(finished.errback) return finished def step2(self, result, finished): if result == 'yadda': finished.callback('This bit works') else: self.d.runOperation(blah).chainDeferred(finished) It takes a while to really understand how Deferreds work, but I'll try to explain this example. In step1, we create a new Deferred called 'finished'. This Deferred didn't come from a method we called; we created it from scratch. This means that it won't be triggered automatically when some event finishes. Instead, we have the freedom to call it whenever we want. We then kick off the asyncronous operation self.db.runQuery. We set up the callbacks as follows: 1. If runQuery completes successfully, call step2(), passing finished as an additional argument. 2. If runQuery fails, pass the error on to finished. Then we return finished, our Deferred. At this point finished.errback will get called if runQuery fails. But what if runQuery succeeds? That will be handled by step2. In step2, we take the result of runQuery , as well as the 'finished' Deferred we created in step1. Now we have the results of the first operation, as well as a reference to the deferred that was returned in step1. If the result of runQuery was 'yadda', we callback finished with a successful value. Otherwise, we kick of a second asyncronous operation, (self.d.runOperation) and chain the results to finished. Remember that a.chainDeferred(b) doesn't do anything special, it's just shorthand for: a.addCallback(b.callback) a.addErrback(b.errback) So the result is that finished ends up calling back or erring back with the results of self.d.runOperation, even though it wasn't bound to runOperation when it was first created. The only thing to be careful of with this technique is that you have to make sure any operation that could possibly fail sends its error to finished.errback - otherwise finished will never be finished :-) I hope that's helpful! I'm happy to answer questions if you have any. Abe p.s. - It's possible there's a better way to do this sort of thing. But I'm using the above technique extensively in Hep and Yarn without any problems. Steve Freitas wrote:
This wasn't spectacularly obvious to me the first time either, but I think it's covered somewhere in the deferred execution howto
Ah ha! I think I figured it out. If I do this, it seems to work...
class XMLRPCResponseClass(): def step1(self): self.d = self.db.runQuery(blah) self.d.addCallback(self.step2) return self.d
def step2(self, query): # Here's where the change is if query == 'yadda': return 'This bit works' else: self.d = self.db.runOperation(blah) # Rebinding self.d to new # Deferred() self.d.addCallback(self.step3) # add callbacks to new one return self.d # And return ref to the new one
def step3(self, data): return 'Now it gets here!'
The trick is that I need to add any subsequent callbacks to a new deferred which I return from the function. Strangely (to me), if I try using chainDeferred() instead of returning the new deferred from step2, it fails with AlreadyCalled, which I don't yet understand, but that's fine, 'cause this seems to work well enough.
I really like this technique because I don't have to use a big state machine at the root level of the xmlrpc calls, or preassign possibly unnecessary callbacks, or toss around callables as variables until I'm thoroughly confused. Instead, it allows me to place all the (client||server) state information for a given method call inside a class instance which persists only until the xmlrpc method returns. And multiple calls to this method while it's being simultaneously Deferred() for other clients don't require any extra coding to handle, as I've got one class instance per connection. Very clean, very well separated, which is hard to figure out how to do with this wonderful framework. Alright, I'll stop ranting now. :-) I still have to give this thing a workout to make sure it does all I want it to.
Thanks for the help!
Steve
_______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python