Last active
January 13, 2020 14:19
-
-
Save fiorix/575270 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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