Skip to content

Instantly share code, notes, and snippets.

@fiorix
Last active January 13, 2020 14:19
Show Gist options
  • Save fiorix/575270 to your computer and use it in GitHub Desktop.
Save fiorix/575270 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# coding: utf-8
# How Twisted's inlineCallbacks work?
# If you use it but don't know exactly what it does,
# and didn't understand the code shipped with Twisted,
# keep reading.
import types, functools
from twisted.python import failure
from twisted.web.client import getPage
from twisted.internet import defer, reactor
class _inlineReturn(Exception):
def __init__(self, value):
self.value = value
def inlineReturn(value):
raise _inlineReturn(value)
class process:
"""
This class will control the execution of functions
decorated with the ``inline`` method.
@inline
def myfunc():
yield something_that_returns_a_Deferred()
Such functions must be generators, yielding
at least one Deferred instance.
Functions decorated with the ``inline`` method will be
turned into Deferred instances. They return a Deferred.
Because it's a generator, you can never use ``return``.
If you want to stop the generator's execution, call
``inlineReturn(value)`` instead. As the function IS
a Deferred itself, it will ``self.callback(value)``.
For more information about python 2.5 generators:
http://docs.python.org/whatsnew/2.5.html#pep-342-new-generator-features
@param work: a generator that yield Deferred instances
"""
def __init__(self, work):
self.work = work
self.deferred = defer.Deferred()
# Pause generator's execution.
# ``data`` is the first Deferred instance
# yielded by the generator.
# Only callback() or errback() will resume
# the generator's execution.
data = work.next()
self.schedule(data)
def ok(self, data):
try:
# Resume generator's execution.
# ``data`` is the next Deferred yielded by the
# generator. Only callback() or errback()
# will resume the generator's execution.
data = self.work.send(data)
self.schedule(data)
except StopIteration, e:
# When there's nothing else to run in the
# generator, we callback() the Deferred
# returned by the ``inline`` method.
self.deferred.callback(None)
self.work.close()
except _inlineReturn, e:
# Stop the generator and callback()
# the Deferred returned by ``inline``.
data = self.deferred.callback(e.value)
self.work.close()
def err(self, failure):
try:
# When errback() is called on the Deferred previously
# yielded by the generator, we try to throw an
# exception into it. It will allow the following syntax:
#
# try:
# d = yield getPage("invalid_url")
# except Exception, e:
# print "err:", e
#
# Again, ``data`` is the next Deferred instance
# yielded by the generator. Only callback() or
# errback() will resume the execution.
data = self.work.throw(failure.type, failure.value, failure.tb)
self.schedule(data)
except Exception, e:
# if the generator is not expecting an exception,
# it will fail. This will happen when the deferred
# yielded by the generator is not surrounded with try/except.
# Like this:
#
# d = yield getPage("invalid_url")
#
# In this case, we errback(failure) the Deferred returned
# by the ``inline`` method and stop the generator's execution.
self.deferred.errback(failure)
self.work.close()
def schedule(self, d):
# ``d`` should be a Deferred, yielded by the generator.
# When it's not a Deferred instance, it's because something
# like this was executed:
#
# yield something_that_dont_return_Deferred()
#
# or, simply
#
# yield 1
#
# If it is a Deferred, we set the both the callback() and
# errback() to self.ok() and self.err(), respectively.
# Such functions will resume the generator's execution.
if isinstance(d, defer.Deferred):
d.addCallbacks(self.ok, self.err)
else:
self.deferred.errback(failure.Failure(
TypeError,
"functions decorated with @inline "
"should yield Deferred instances, not ``%s``" % type(d)))
self.work.close()
def inline(method):
@functools.wraps(method)
def wrapper(*args, **kwargs):
work = method(*args, **kwargs)
if isinstance(work, types.GeneratorType):
proc = process(work)
return proc.deferred
else:
d = defer.Deferred()
d.errback(failure.Failure(
TypeError,
"method ``%s`` should be a Generator." % method))
return d
return wrapper
@inline
def test(url):
#yield 1
d = yield getPage(url)
inlineReturn("here it is: "+d)
print "I will never execute"
@inline
def main():
try:
x = yield getPage("http://freegeoip.net/make_it_404")
print "x is:", x
except Exception, e:
print "x err:", e
# If you enforce ``getPage`` to fail, using a bad URL,
# it will immediately stop main(), because it's not
# expecting an exception (like the example above, surrounded
# by try/except.
y = yield test("http://freegeoip.net/csv/google.com")
print "y is:", y
z = yield getPage("http://freegeoip.net/json/yahoo.com")
print "z is:", z
if __name__ == "__main__":
main().addCallback(lambda ign: reactor.stop())
reactor.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment