Package: pretzel
PretzelIs an asynchronous application framework for pythonFeaturesC# like async/await(async/yield) paradigm for asynchronous
programming (monad base)Cool asynchronous I/O loop implementationUniform asynchronous stream implementation for sockets and pipesInteract with subprocesses asynchronouslyGreenlet support (but not required)Remote code executing over ssh or in child process (with only
requirements python and ssh)Python 2/3, PyPy (starting from 2.0) compatibleAsynchronous python shellpython-mpretzel.apps.shell(requires
greenlet)InstallationAs git submodule:git submodule addgit://github.com/aslpavel/pretzel.git<path_to_submodule>Pip from git:pip installgit+git://github.com/aslpavel/pretzel-pkg.gitPip from
PyPIpip install pretzelApproachUsage of asynchronous functions is similar to C# async/await but instead
ofasyncattribute it uses@asyncdecorator and instead ofawaitkeyword,yieldis used. Internally unit of asynchrony is
implemented as continuation monadContwith embeddedResultmonad (similar to Haskell’sContandEithermonads) as its
value. One important difference ofContmonad from C#Taskobject, is thatTaskrepresents already running asynchronous
operation, but continuation monad is a sequence of computations, and
this computations are not started..future()method on instance ofContcan be used to createTasklike object. To use this library
you don’t have to understand notion of the monad. Simple asynchronous
function would look like this.frompretzel.monadimportasyncfrompretzel.coreimportsleep@asyncdefprint_after(delay,*args,**kwargs):"""Calls print function after the lapse of `delay` sedonds.
  """yieldsleep(delay)# execution will be resumed in delay secondsprint(*args,**kwargs)To return something meaningful in python3 you can just usereturnkeyword, but in python2 you have to usedo_returnfunction (it will
also work in python3) asreturnwith value cannot be used inside a
generator function. Result of such asynchronous function is again a
continuation monad, if exception is thrown during execution of its body,
it is marshaled to receiver of the result and can be processed
correctly. For example.@asyncdefprocess_error():@asyncdefthrow_after(delay,error):yieldsleep(delay)raiseerrortry:yieldthrow_after(1,ValueError('test error'))exceptValueErroraserror:# process error in a meaningfull waydo_return('done')# exectly equivalent to: return 'done'Asynchronous values (continuation monads) can be composed with two
helper functionsasync_allandasync_any.@asyncdefcomposition_example():yieldasync_all([sleep(1),sleep(2)])# will be resumed in 2 secondsyieldasync_any([sleep(1),sleep(2)])# will be resumed in 1 sedondresult_all=yieldasync_all([func1(),func2()])# = (result1, result2)reuslt_any=yieldasync_any([func1(),func2()])# = result1 | result2Contmonad can also be called with callback function as its
argument, in this case, on completion of asynchronous operation,
callback will be called withResultmonad. If callback function is
not specified default, then default continuation callback will be used
which only reports errors if any.>>>sleep(1)(print)Result(val:1374307530.015137)>>>sleep(None)()[continuation]errorincoroutinestartedfromFile"<console>",line1,in<module>`-------------------------------------------------------------------------------Host:fiendProcess:13492Error:TypeError("unsupported operand type(s) for +: 'float' and 'NoneType'")Traceback(mostrecentcalllast):File"./pretzel/monad/do.py",line26,indo_blockreturnvalue(block(*args,**kwargs))File"./pretzel/core/core.py",line118,insleepdo_done(self.time_queue.on(time()+delay))TypeError:unsupportedoperandtype(s)for+:'float'and'NoneType'Inside body of asynchronous function you canyieldnot onlyContmonad directly, but any object implementing.__monad__()method
which returnsContmonad. There are many such types in this library,
for exampleEvent@asyncdeffunc():print(1)yieldeventprint(2)print((yieldevent))event=Event()func()()# 1 is printedevent('e0')# 2 is printedevent('e1')# 'e1' is printedMain loopCoreclass implements I/O loop, and it is used internally to
implement asynchronous streams, timers and more. Previously usedsleepfunction will work correctly only in presence of running I/O
loop. Simplest way to intialize and useCoreobject is to use@appdecorator."""Minimal pretzel application

Sleeps for one second, then prints 'done' and exits.
"""frompretzel.appimportappfrompretzel.coreimportsleep@appdefmain():yieldsleep(1)print('done')if__name__=='__main__':main()SeeCore’s init
modulefor full list of available functions and their descriptions.RemotingMain reason for creation of this framework was to execute code on a set
of machines via ssh connection. It is achieved by usage ofSSHConnectionclass.SSHConnectionis a callable object which
returns proxy object for its argument. You can call proxy object, get
its attributes or itemsproxy[item], result of such operations is
again a proxy object with this embedded operations. Proxy implements
monad interface, and to get result of embedded chain of operations you
can yield it inside asynchronous function. In this example we create
proxy foros.getpidfunction, call it and then execute on remote
process by yielding it. There is no need for pretzel to be installed on
remote machine.importosfrompretzel.appimportappfrompretzel.remotingimportSSHConnection@appdefmain():"""Connect to localhost via ssh and print remote process's pid

  Note:
    You have to be able to login to the remote host without
    entering any password (by means of ssh keys) otherwise
    connecition will fail.
  """with(yieldSSHConnection('localhost'))asconn:print((yieldconn(os.getpid)()))if__name__=='__main__':main()Connection can marshal any pickle-able object, orSenderobject plus
any object which is reducible to set of pickle-able andSenderobjects.ProxyandConnectionitself are examples of such
objects. You can also create proxy object from any arbitrary object withproxifyorproxify_func.importosfrompretzel.appimportappfrompretzel.remotingimportSSHConnection,proxifyclassRemote(object):"""Object which will be used remotely
  """def__init__(self):self.value=0defnext(self):self.value+=1returnself.valuedefgetpid(self):returnos.getpid()@appdefmain():with(yieldSSHConnection('localhost'))asconn:with(yieldproxify(conn(Remote)()))aso:# remote object proxyprint(os.getpid(),(yieldo.getpid()))# prints two different pidsprint((yieldo.next()))# prints 1print((yieldo.next()))# prints 2if__name__=='__main__':main()ButContmonad is not marshallable, that is why there is special
operation on proxy object~which is equivalent toyieldinside
asynchronous function. Here is an example of remote execution of
asynchronous function.frompretzel.appimportappfrompretzel.processimportprocess_callfrompretzel.remotingimportSSHConnection@appdefmain():"""Execute 'ls' on remote machine and show result of the execution
  """with(yieldSSHConnection('localhost'))asconn:out,err,code=yield~conn(process_call)('ls')print(out.decode())if__name__=='__main__':main()There is also a way to work with multiple connections as if it one, by
means ofcomposite_ssh_conn. It accepts list of hosts and returns
composite connection, which behaves as ordinary connection but returns
set of results.importosfrompretzel.appimportappfrompretzel.remotingimportcomposite_ssh_conn@appdefmain():hosts=['localhost','localhost']with(yieldcomposite_ssh_conn(hosts))asconns:result=yieldconns(os.getpid)()print(result)# List(25163, 25162) - iterable object of pidsif__name__=='__main__':main()Remoting submodule can be used as workaround for python’s GIL, in a
similar fashion tomultiprocessingmodule. You can useForkConnection(orcomposite_fork_conn) which behaves asSSHConnectionbut instead of connecting via ssh, it just spawns new
process.importtimefrompretzel.appimportappfrompretzel.remotingimportcomposite_fork_conndefcomputation_heavy_task():"""Some computation intensive task
  """start_time=time.time()time.sleep(10)stop_time=time.time()returnint(stop_time-start_time)@appdefmain():with(yieldcomposite_fork_conn(10))asconns:# create 10 connectionsresult=yieldconns(computation_heavy_task)()print(result)# prints List(10, 10, 10, 10, 10, 10, 10, 10, 10, 10)if__name__=='__main__':main()ExamplesSimple echo serverCat remote file over
ssh