This article is going to deal with another aspect of Deferreds, which normally is a bit non-trivial to realise,so we are going to present a complex situation, which will demonstrate the problem, and how deferred solves the problem.
To motivate our discussion we’re going to add a hypothetical feature to our poetry client. Suppose some hard-working Computer Science professor has invented a new poetry-related algorithm, the Byronification Engine. This nifty algorithm takes a single poem as input and produces a new poem like the original, but written in the style of Lord Byron. What’s more, our professor has kindly provided a reference implementation in Python, with this interface:
class IByronificationEngine(Interface): def byronificate(poem): """ Return a new poem like the original, but in the style of Lord Byron. Raises GibberishError if the input is not a genuine poem. """
Like most bleeding-edge software, the implementation has some bugs. This means that in addition to the documented exception, the
byronificate method sometimes throws random exceptions when it hits a corner-case the professor forgot to handle.
We’ll also assume the engine runs fast enough that we can just call it in the main thread without worrying about tying up the reactor. This is how we want our program to work:
- Try to download the poem.
- If the download fails, tell the user we couldn’t get the poem.
- If we do get the poem, transform it with the Byronification Engine.
- If the engine throws a
GibberishError, tell the user we couldn’t get the poem.
- If the engine throws another exception, just keep the original poem.
- If we have a poem, print it out.
- End the program.
The idea here is that a
GibberishError means we didn’t get an actual poem after all, so we’ll just tell the user the download failed. That’s not so useful for debugging, but our users just want to know whether we got a poem or not. On the other hand, if the engine fails for some other reason then we’ll use the poem we got from the server. After all, some poetry is better than none at all, even if it’s not in the trademark Byron style.
Here’s the synchronous version of our code:
try: poem = get_poetry(host, port) # synchronous get_poetry except: print >>sys.stderr, 'The poem download failed.' else: try: poem = engine.byronificate(poem) except GibberishError: print >>sys.stderr, 'The poem download failed.' except: print poem # handle other exceptions by using the original poem else: print poem sys.exit()
This sketch of a program could be make simpler with some refactoring, but it illustrates the flow of logic pretty clearly. We want to update our most recent poetry client (which uses deferreds) to implement this same scheme. But we won’t do that until Part 10. For now, instead, let’s imagine how we might do this with client 3.1, our last client that didn’t use deferreds at all. Suppose we didn’t bother handling exceptions, but instead just changed the
got_poem callback like this:
def got_poem(poem): poems.append(byron_engine.byronificate(poem)) poem_done()
What happens when the
byronificate method raises a
GibberishError or some other exception? Looking at Figure 11 from Part 6, we can see that:
- The exception will propagate to the
poem_finishedcallback in the factory, the method that actually invokes the callback.
poem_finisheddoesn’t catch the exception, it will proceed to
poemReceivedon the protocol.
- And then on to
connectionLost, also on the protocol.
- And then up into the core of Twisted itself, finally ending up at the reactor.
As we have learned, the reactor will catch and log the exception instead of crashing. But what it certainly won’t do is tell the user we couldn’t download a poem. The reactor doesn’t know anything about poems or
GibberishErrors, it’s a general-purpose piece of code used for all kinds of networking, even non-poetry-related networking.
I think the concept is rather simple, and dealing with it just requires plain reading. So I won’t be going after copying the content. You can refer to the website for rest of the stuff (https://krondo.com/a-second-interlude-deferred/)