Package: metapensiero.asyncio.transaction
author:Alberto Berticontact:alberto@metapensiero.itlicense:GNU General Public License version 3 or laterHandle coroutines from synchronous functions or methods (like special methods)GoalThis package helps handling such cases when there is a side effect
that is coded as a coroutine and the code block that needs it cannot
be run as a coroutine.The case is e. g. when a coroutine is called from an external package
or a Python special method.This package will give you the tools to ensure that even if the
computation is not immediate, the execution of the coroutine(s) is
guaranteed to happen in the context of the coroutine that is calling
the special method.A package that works in tandem with this ismetapensiero.signalInstallationTo install the package execute the following command:$ pip install metapensiero.asyncio.transactionUsageGiven a scenario where a coroutine is called from the__setattr___method, this is how to deal with the situation:importasynciofrommetapensiero.asyncimporttransaction@asyncio.coroutinedefpublish(value):# do something asyncclassExample:def__setattr__(self,name,value):trans=transaction.get()trans.add(publish(value))super().__setattr__(name,value)@asyncio.coroutinedefexternal_coro():inst=Example()trans=transaction.begin()inst.foo='bar'yield fromtrans.end()In python 3.5, theexternal_corocan be written as:asyncdefexternal_coro():inst=Example()asyncwithtransaction.begin():inst.foo='bar'So by taking advantage of the new__aenter__and__aexit__methods and awaitable classes.The coroutines will be scheduled in the order they have been created.At a certain point, you may want  to ensure that all the remaining
coroutines are executed you may use the coroutinetransaction.wait_all(), doing so will end all the remaining open
transactions.When your code add a coro to the transaction it can also pass a
callback as thecbackkeyword parameter, so it will be called when
the stashed coro completes (or is cancelled). This way you can use the
return value of the coro like:importasynciofrommetapensiero.asyncioimporttransactionresults=[]@asyncio.coroutinedefstashed_coro():nonlocalresultsresults.append('called stashed_coro')return'result from stashed coro'classA:def__init__(self):tran=transaction.get()c=stashed_coro()tran.add(c,cback=self._init)def_init(self,stashed_task):nonlocalresultsresults.append(stashed_task.result())@asyncio.coroutinedefexternal_coro():tran=transaction.begin()# in py3.5# async with tran:#     a = A()a=A()yield fromtran.end()yield fromasyncio.gather(external_coro())assertlen(results)==2assertresults==['called stashed_coro','result from stashed coro']TestingTo run the tests you should run the following at the package root:python setup.py testBuild statusChanges0.1 (2015-12-15)Adaptation from the rocky sources.First draft documentation.0.2 (2015-12-16)Document some details about testing.Add some checks on end().0.3 (2015-12-26)Fix packaging.0.4 (2015-12-27)Documentation fixes.0.5 (2016-01-22)Fix behavior when transaction.begin() is called from code not
already running in a task.0.6 (2016-01-27)support a callback function when adding coros to the transaction.0.7 (2016-02-18)Add a wait() method to suspend the current task while the coros complete
without closing the current transaction.