Package: gevent_async
A small set of utilities to help with writing synchronous code flows in a collaborative multitasking context.
It has been designed around the feature set of gevent (http://www.gevent.org)deferred callsasync.DeferredCallHandleris a wrapper for asynchronously handled function calls.
This allows to control in which context the execution of those functions are done, which is essential
in collaborative multitasking.There are 2 available types of calls:sync(synchronous): this type of call awaits for the deferred call handle to process the call
to return. for a user’s perspective, it behaves like a regular function call.oneway(one way): this type of call returns instantly. Due to its nature, there is no way to know
whether, once it has been processed, it has succeeded or failed.ExampleFor instance, imagining we have a Manager entity that must handle some resources in an atomic manner:fromasyncimportDeferredCallHandlerclassManager(DeferredCallHandler):defmanage(self):# do things with resources# with the assurance that the resources won't# be modified during processself.process()# process pending calls# do more thingsdefaccess_resources(self):#returns the resources the manager has properly managed.defupdate_resource(self,data):#updates a resource infodefrun(self):whileTrue:self.manage()We can startup the manager and call functions on it from multiple greenlets:manager=Manager()gevent.spawn(manager.run)# At that point, the manager entity is will be doing resource managementresources=...# we have an array of resourcesdefmonitor(target):foreventintarget.events():# we could apply some transformation to the event, and then# forward it to the manager.manager.oneway.update_resource(event)forresourceinresources:gevent.spawn(monitor,resource)defconsumer():whileTrue:resources=manager.access_resources()# at that point, we have the guarantee that the resources# are properly managed and will not become stale or corrupted during process.consumer()DeferredCallHandler API documentationdef process(forever=False, whitelist=None):Processes all the the pending deferred calls.Ifforeveris set toTrue, process will remain waiting for new calls until
a call tostop_processing()is performed.Ifwhitelistis set as a list of string, only functions which names match the elements
in the white list will be executed.def stop_processing():Interrupts the iteration through incoming calls of a DeferredCallHandler’s call toprocess(forever=True).Exceptionssync calls will forward exceptions just like regular functions:fromasyncimportDeferredCallHandlerclassLemming(DeferredCallHandler):defkaboom(self):raiseException("#high pitched# oh no!")lemming=Lemming()spawn(lemming.process,forever=True)try:lemming.sync.kaboom()exceptException:pass# We should hit that# This should trigger the exception but produce an exception log entry.lemming.oneway.kaboom()Regular function callsDeferredCallHandlerobjects don’t prevent direct function calls. Use at your own risk:fromasyncimportDeferredCallHandlerclassManager(DeferredCallHandler):defmanage(self):# do things with resources# with the assurance that the resources won't# be modified during processself.process()# process pending calls# do more thingsdefaccess_resources(self):#returns the resources the manager has properly managed.defupdate_resource(self,data):#updates a resource infodefrun(self):whileTrue:self.manage()manager=Manager()gevent.spawn(manager.run)resources=manager.access_resources()# !!! The resources may be in the middle of a management process and their state# may be incoherentresources=manager.sync.access_resources()# In that case, we're guaranteed the management process is not running.Timeoutssynccalls can be specified with an optional timeout, to ensure actions are performed
within a given time frame:fromasyncimportDeferredCallHandlerclassABitSlow(DeferredCallHandler):deftaking_my_time(self):gevent.sleep(10)slow=ABitSlow()spawn(slow.process,forever=True)try:slow.sync(timeout=1).taking_my_time()exceptgevent.Timeout:pass# We should hit thatmultitask state handlingPartially inspired by the mechanism of tail recursion, we provide a way to contain and handle code
to manage the behaviour of state machines within greenlets.The@statedecorator transforms a function method into a state greenlet. When another state function
is invoked, it create a new state greenlet that replaces the current state greenlet, effectively replicating
the behaviour of tail recursion.For instance:@state(transitions_to="growing")defsprouting()# germination process heregrowing()# the sprouting greenlet terminates and leaves way to the growing one@state(transitions_to="flowering")defgrowing()# transform CO2 and sunlight to biomassflowering()# the growing greenlet terminates and leaves way to the flowering one@state(transitions_to=["dead","withering"])defflowering()# Grow flowersifis_eaten:# parameters can be given to state changes.dead(is_eaten=True)# the flowering greenlet terminates and leaves way to the dead oneelse:withering()# the flowering greenlet terminates and leaves way to the withering one@state(transitions_to="dead")defwithering()# Dry updead()# the withering greenlet terminates and leaves way to the dead one@state# terminal state, no transitionsdefdead(is_eaten=False)ifnotis_eaten:# clean up phasesprouting()# spawns the initial stateThe@statedecorator can also be used for methods:classFlower(object):@state(transitions_to="growing")defsprouting(self)# germination process heregrowing()# the sprouting greenlet terminates and leaves way to the growing one# ...Correct transitions must be specified by thetransitions_toparameter or any incorrect transition
will raise theValidationErrorexception.CallbacksCallbacks can be defined on transition. By setting the on_start parameter to a state, a given callback will
be activated whenever a state is started.The expected callback signature isdef on_start(state, *args, **kwargs), wherestateis the
(at that point, still not started)async.state.Statestate greenlet which will handle the execution of the state and*argsand**kwargsare the parameters given to the state call.For instance:defon_transition(new_state,target,*args,**kwargs):if"store"inkwargsandkwargs["store"]:target.state=new_stateclassObject(object):def__init__(self):self.state=None@state(on_start=on_transition)defa_state(self,store=False):passobj=Object()obj.a_state(store=True)sleep()obj.state# => is now storing the current state object.