def deferredGenerator(f): """ deferredGenerator and waitForDeferred help you write L{Deferred}-using code that looks like a regular sequential function. If your code has a minimum requirement of Python 2.5, consider the use of L{inlineCallbacks} instead, which can accomplish the same thing in a more concise manner. There are two important functions involved: L{waitForDeferred}, and L{deferredGenerator}. They are used together, like this:: def thingummy(): thing = waitForDeferred(makeSomeRequestResultingInDeferred()) yield thing thing = thing.getResult() print thing #the result! hoorj! thingummy = deferredGenerator(thingummy) L{waitForDeferred} returns something that you should immediately yield; when your generator is resumed, calling C{thing.getResult()} will either give you the result of the L{Deferred} if it was a success, or raise an exception if it was a failure. Calling C{getResult} is B{absolutely mandatory}. If you do not call it, I{your program will not work}. L{deferredGenerator} takes one of these waitForDeferred-using generator functions and converts it into a function that returns a L{Deferred}. The result of the L{Deferred} will be the last value that your generator yielded unless the last value is a L{waitForDeferred} instance, in which case the result will be C{None}. If the function raises an unhandled exception, the L{Deferred} will errback instead. Remember that C{return result} won't work; use C{yield result; return} in place of that. Note that not yielding anything from your generator will make the L{Deferred} result in C{None}. Yielding a L{Deferred} from your generator is also an error condition; always yield C{waitForDeferred(d)} instead. The L{Deferred} returned from your deferred generator may also errback if your generator raised an exception. For example:: def thingummy(): thing = waitForDeferred(makeSomeRequestResultingInDeferred()) yield thing thing = thing.getResult() if thing == 'I love Twisted': # will become the result of the Deferred yield 'TWISTED IS GREAT!' return else: # will trigger an errback raise Exception('DESTROY ALL LIFE') thingummy = deferredGenerator(thingummy) Put succinctly, these functions connect deferred-using code with this 'fake blocking' style in both directions: L{waitForDeferred} converts from a L{Deferred} to the 'blocking' style, and L{deferredGenerator} converts from the 'blocking' style to a L{Deferred}. """ def unwindGenerator(*args, **kwargs): return _deferGenerator(f(*args, **kwargs), Deferred()) return mergeFunctionMetadata(f, unwindGenerator)
def suppressWarnings(f, *suppressedWarnings): """ Wrap C{f} in a callable which suppresses the indicated warnings before invoking C{f} and unsuppresses them afterwards. If f returns a Deferred, warnings will remain suppressed until the Deferred fires. """ def warningSuppressingWrapper(*a, **kw): return runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw) return tputil.mergeFunctionMetadata(f, warningSuppressingWrapper)
def test_nameIsMerged(self): """ Merging C{foo} into C{bar} returns a function with C{foo}'s name. """ def foo(): pass def bar(): pass baz = util.mergeFunctionMetadata(foo, bar) self.assertEqual(baz.__name__, foo.__name__)
def test_moduleIsMerged(self): """ Merging C{foo} into C{bar} returns a function with C{foo}'s C{__module__}. """ def foo(): pass def bar(): pass bar.__module__ = 'somewhere.else' baz = util.mergeFunctionMetadata(foo, bar) self.assertEqual(baz.__module__, foo.__module__)
def deprecationDecorator(function): """ Decorator that marks C{function} as deprecated. """ warningString = getDeprecationWarningString(function, version) def deprecatedFunction(*args, **kwargs): warn(warningString, DeprecationWarning, stacklevel=2) return function(*args, **kwargs) deprecatedFunction = mergeFunctionMetadata(function, deprecatedFunction) _appendToDocstring(deprecatedFunction, _getDeprecationDocstring(version)) deprecatedFunction.deprecatedVersion = version return deprecatedFunction
def test_docstringIsMerged(self): """ Merging C{foo} into C{bar} returns a function with C{foo}'s docstring. """ def foo(): """ This is foo. """ def bar(): """ This is bar. """ baz = util.mergeFunctionMetadata(foo, bar) self.assertEqual(baz.__doc__, foo.__doc__)
def inlineCallbacks(f): """ WARNING: this function will not work in Python 2.4 and earlier! inlineCallbacks helps you write Deferred-using code that looks like a regular sequential function. This function uses features of Python 2.5 generators. If you need to be compatible with Python 2.4 or before, use the L{deferredGenerator} function instead, which accomplishes the same thing, but with somewhat more boilerplate. For example:: def thingummy(): thing = yield makeSomeRequestResultingInDeferred() print thing #the result! hoorj! thingummy = inlineCallbacks(thingummy) When you call anything that results in a L{Deferred}, you can simply yield it; your generator will automatically be resumed when the Deferred's result is available. The generator will be sent the result of the L{Deferred} with the 'send' method on generators, or if the result was a failure, 'throw'. Your inlineCallbacks-enabled generator will return a L{Deferred} object, which will result in the return value of the generator (or will fail with a failure object if your generator raises an unhandled exception). Note that you can't use C{return result} to return a value; use C{returnValue(result)} instead. Falling off the end of the generator, or simply using C{return} will cause the L{Deferred} to have a result of C{None}. The L{Deferred} returned from your deferred generator may errback if your generator raised an exception:: def thingummy(): thing = yield makeSomeRequestResultingInDeferred() if thing == 'I love Twisted': # will become the result of the Deferred returnValue('TWISTED IS GREAT!') else: # will trigger an errback raise Exception('DESTROY ALL LIFE') thingummy = inlineCallbacks(thingummy) """ def unwindGenerator(*args, **kwargs): return _inlineCallbacks(None, f(*args, **kwargs), Deferred()) return mergeFunctionMetadata(f, unwindGenerator)
def deprecationDecorator(function): """ Decorator that marks C{function} as deprecated. """ warningString = getDeprecationWarningString(function, version) def deprecatedFunction(*args, **kwargs): warn( warningString, DeprecationWarning, stacklevel=2) return function(*args, **kwargs) deprecatedFunction = mergeFunctionMetadata( function, deprecatedFunction) _appendToDocstring(deprecatedFunction, _getDeprecationDocstring(version)) deprecatedFunction.deprecatedVersion = version return deprecatedFunction
class TestMergeFunctionMetadata(unittest.TestCase): """ Tests for L{mergeFunctionMetadata}. """ def test_mergedFunctionBehavesLikeMergeTarget(self): """ After merging C{foo}'s data into C{bar}, the returned function behaves as if it is C{bar}. """ foo_object = object() bar_object = object() def foo(): return foo_object def bar(x, y, (a, b), c=10, *d, **e): return bar_object baz = util.mergeFunctionMetadata(foo, bar) self.assertIdentical(baz(1, 2, (3, 4), quux=10), bar_object)
def test_instanceDictionaryIsMerged(self): """ Merging C{foo} into C{bar} returns a function with C{bar}'s dictionary, updated by C{foo}'s. """ def foo(): pass foo.a = 1 foo.b = 2 def bar(): pass bar.b = 3 bar.c = 4 baz = util.mergeFunctionMetadata(foo, bar) self.assertEqual(foo.a, baz.a) self.assertEqual(foo.b, baz.b) self.assertEqual(bar.c, baz.c)