{ "info": { "author": "Pavel Aslanov", "author_email": "asl.pavel@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "Pretzel\n-------\n\n|Build Status| |Coverage Status|\n\nIs an asynchronous application framework for python\n\nFeatures\n--------\n\n- C# like async/await(async/yield) paradigm for asynchronous\n programming (monad base)\n- Cool asynchronous I/O loop implementation\n- Uniform asynchronous stream implementation for sockets and pipes\n- Interact with subprocesses asynchronously\n- Greenlet support (but not required)\n- Remote code executing over ssh or in child process (with only\n requirements python and ssh)\n- Python 2/3, PyPy (starting from 2.0) compatible\n- Asynchronous python shell ``python -mpretzel.apps.shell`` (requires\n greenlet)\n\nInstallation\n------------\n\nAs git submodule:\n``git submodule add git://github.com/aslpavel/pretzel.git ``\nPip from git:\n``pip install git+git://github.com/aslpavel/pretzel-pkg.git`` Pip from\nPyPI ``pip install pretzel``\n\nApproach\n--------\n\nUsage of asynchronous functions is similar to C# async/await but instead\nof ``async`` attribute it uses ``@async`` decorator and instead of\n``await`` keyword, ``yield`` is used. Internally unit of asynchrony is\nimplemented as continuation monad ``Cont`` with embedded ``Result``\nmonad (similar to Haskell's ``Cont`` and ``Either`` monads) as its\nvalue. One important difference of ``Cont`` monad from C# ``Task``\nobject, is that ``Task`` represents already running asynchronous\noperation, but continuation monad is a sequence of computations, and\nthis computations are not started. ``.future()`` method on instance of\n``Cont`` can be used to create ``Task`` like object. To use this library\nyou don't have to understand notion of the monad. Simple asynchronous\nfunction would look like this.\n\n.. code:: python\n\n from pretzel.monad import async\n from pretzel.core import sleep\n\n @async\n def print_after(delay, *args, **kwargs):\n \"\"\"Calls print function after the lapse of `delay` sedonds.\n \"\"\"\n yield sleep(delay) # execution will be resumed in delay seconds\n print(*args, **kwargs)\n\nTo return something meaningful in python3 you can just use ``return``\nkeyword, but in python2 you have to use ``do_return`` function (it will\nalso work in python3) as ``return`` with value cannot be used inside a\ngenerator function. Result of such asynchronous function is again a\ncontinuation monad, if exception is thrown during execution of its body,\nit is marshaled to receiver of the result and can be processed\ncorrectly. For example.\n\n.. code:: python\n\n @async\n def process_error():\n @async\n def throw_after(delay, error):\n yield sleep(delay)\n raise error\n\n try:\n yield throw_after(1, ValueError('test error'))\n except ValueError as error:\n # process error in a meaningfull way\n do_return('done') # exectly equivalent to: return 'done'\n\nAsynchronous values (continuation monads) can be composed with two\nhelper functions ``async_all`` and ``async_any``.\n\n.. code:: python\n\n @async\n def composition_example():\n yield async_all([sleep(1), sleep(2)]) # will be resumed in 2 seconds\n yield async_any([sleep(1), sleep(2)]) # will be resumed in 1 sedond\n\n result_all = yield async_all([func1(), func2()]) # = (result1, result2)\n reuslt_any = yield async_any([func1(), func2()]) # = result1 | result2\n\n``Cont`` monad can also be called with callback function as its\nargument, in this case, on completion of asynchronous operation,\ncallback will be called with ``Result`` monad. If callback function is\nnot specified default, then default continuation callback will be used\nwhich only reports errors if any.\n\n.. code:: python\n\n >>> sleep(1)(print)\n Result(val:1374307530.015137)\n >>> sleep(None)()\n [continuation] error in coroutine started from\n File \"\", line 1, in \n `-------------------------------------------------------------------------------\n Host : fiend\n Process: 13492\n Error : TypeError(\"unsupported operand type(s) for +: 'float' and 'NoneType'\")\n\n Traceback (most recent call last):\n File \"./pretzel/monad/do.py\", line 26, in do_block\n return value(block(*args, **kwargs))\n File \"./pretzel/core/core.py\", line 118, in sleep\n do_done(self.time_queue.on(time() + delay))\n TypeError: unsupported operand type(s) for +: 'float' and 'NoneType'\n\nInside body of asynchronous function you can ``yield`` not only ``Cont``\nmonad directly, but any object implementing ``.__monad__()`` method\nwhich returns ``Cont`` monad. There are many such types in this library,\nfor example ``Event``\n\n.. code:: python\n\n @async\n def func():\n print(1)\n yield event\n print(2)\n print((yield event))\n event = Event()\n func()() # 1 is printed\n event('e0') # 2 is printed\n event('e1') # 'e1' is printed\n\nMain loop\n---------\n\n``Core`` class implements I/O loop, and it is used internally to\nimplement asynchronous streams, timers and more. Previously used\n``sleep`` function will work correctly only in presence of running I/O\nloop. Simplest way to intialize and use ``Core`` object is to use\n``@app`` decorator.\n\n.. code:: python\n\n \"\"\"Minimal pretzel application\n\n Sleeps for one second, then prints 'done' and exits.\n \"\"\"\n from pretzel.app import app\n from pretzel.core import sleep\n\n @app\n def main():\n yield sleep(1)\n print('done')\n\n if __name__ == '__main__':\n main()\n\nSee `Core's init\nmodule `__\nfor full list of available functions and their descriptions.\n\nRemoting\n--------\n\nMain reason for creation of this framework was to execute code on a set\nof machines via ssh connection. It is achieved by usage of\n``SSHConnection`` class. ``SSHConnection`` is a callable object which\nreturns proxy object for its argument. You can call proxy object, get\nits attributes or items ``proxy[item]``, result of such operations is\nagain a proxy object with this embedded operations. Proxy implements\nmonad interface, and to get result of embedded chain of operations you\ncan yield it inside asynchronous function. In this example we create\nproxy for ``os.getpid`` function, call it and then execute on remote\nprocess by yielding it. There is no need for pretzel to be installed on\nremote machine.\n\n.. code:: python\n\n import os\n from pretzel.app import app\n from pretzel.remoting import SSHConnection\n\n @app\n def main():\n \"\"\"Connect to localhost via ssh and print remote process's pid\n\n Note:\n You have to be able to login to the remote host without\n entering any password (by means of ssh keys) otherwise\n connecition will fail.\n \"\"\"\n with (yield SSHConnection('localhost')) as conn:\n print((yield conn(os.getpid)()))\n\n if __name__ == '__main__':\n main()\n\nConnection can marshal any pickle-able object, or ``Sender`` object plus\nany object which is reducible to set of pickle-able and ``Sender``\nobjects. ``Proxy`` and ``Connection`` itself are examples of such\nobjects. You can also create proxy object from any arbitrary object with\n``proxify`` or ``proxify_func``.\n\n.. code:: python\n\n import os\n from pretzel.app import app\n from pretzel.remoting import SSHConnection, proxify\n\n class Remote(object):\n \"\"\"Object which will be used remotely\n \"\"\"\n def __init__(self):\n self.value = 0\n\n def next(self):\n self.value += 1\n return self.value\n\n def getpid(self):\n return os.getpid()\n\n @app\n def main():\n with (yield SSHConnection('localhost')) as conn:\n with (yield proxify(conn(Remote)())) as o: # remote object proxy\n print(os.getpid(), (yield o.getpid())) # prints two different pids\n print((yield o.next())) # prints 1\n print((yield o.next())) # prints 2\n\n if __name__ == '__main__':\n main()\n\nBut ``Cont`` monad is not marshallable, that is why there is special\noperation on proxy object ``~`` which is equivalent to ``yield`` inside\nasynchronous function. Here is an example of remote execution of\nasynchronous function.\n\n.. code:: python\n\n from pretzel.app import app\n from pretzel.process import process_call\n from pretzel.remoting import SSHConnection\n\n @app\n def main():\n \"\"\"Execute 'ls' on remote machine and show result of the execution\n \"\"\"\n with (yield SSHConnection('localhost')) as conn:\n out, err, code = yield ~conn(process_call)('ls')\n print(out.decode())\n\n if __name__ == '__main__':\n main()\n\nThere is also a way to work with multiple connections as if it one, by\nmeans of ``composite_ssh_conn``. It accepts list of hosts and returns\ncomposite connection, which behaves as ordinary connection but returns\nset of results.\n\n.. code:: python\n\n import os\n from pretzel.app import app\n from pretzel.remoting import composite_ssh_conn\n\n @app\n def main():\n hosts = ['localhost', 'localhost']\n with (yield composite_ssh_conn(hosts)) as conns:\n result = yield conns(os.getpid)()\n print(result) # List(25163, 25162) - iterable object of pids\n\n if __name__ == '__main__':\n main()\n\nRemoting submodule can be used as workaround for python's GIL, in a\nsimilar fashion to ``multiprocessing`` module. You can use\n``ForkConnection`` (or ``composite_fork_conn``) which behaves as\n``SSHConnection`` but instead of connecting via ssh, it just spawns new\nprocess.\n\n.. code:: python\n\n import time\n from pretzel.app import app\n from pretzel.remoting import composite_fork_conn\n\n def computation_heavy_task():\n \"\"\"Some computation intensive task\n \"\"\"\n start_time = time.time()\n time.sleep(10)\n stop_time = time.time()\n return int(stop_time - start_time)\n\n @app\n def main():\n with (yield composite_fork_conn(10)) as conns: # create 10 connections\n result = yield conns(computation_heavy_task)()\n print(result) # prints List(10, 10, 10, 10, 10, 10, 10, 10, 10, 10)\n\n if __name__ == '__main__':\n main()\n\nExamples\n--------\n\n- `Simple echo server `__\n- `Cat remote file over\n ssh `__\n\n.. |Build Status| image:: https://api.travis-ci.org/aslpavel/pretzel.png\n :target: https://travis-ci.org/aslpavel/pretzel\n.. |Coverage Status| image:: https://coveralls.io/repos/aslpavel/pretzel/badge.png?branch=master\n :target: https://coveralls.io/r/aslpavel/pretzel?branch=master", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/aslpavel/pretzel", "keywords": null, "license": "Apache License 2.0", "maintainer": null, "maintainer_email": null, "name": "pretzel", "package_url": "https://pypi.org/project/pretzel/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/pretzel/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/aslpavel/pretzel" }, "release_url": "https://pypi.org/project/pretzel/1.0.10/", "requires_dist": null, "requires_python": null, "summary": "Pretzel - asynchronous python framework", "version": "1.0.10" }, "last_serial": 832904, "releases": { "1.0.1": [ { "comment_text": "", "digests": { "md5": "fcd6a7f594433bbfbb65b57d6a642e18", "sha256": "d8fd1834b512054fdc01e5dcd698c16944ff7e5aa865736ccdd4708643f0ee4b" }, "downloads": -1, "filename": "pretzel-1.0.1.tar.gz", "has_sig": false, "md5_digest": "fcd6a7f594433bbfbb65b57d6a642e18", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 68108, "upload_time": "2013-06-13T09:38:08", "url": "https://files.pythonhosted.org/packages/c3/50/14942cf0c5dbbb8a31297f56c62d580ca0f0b98c6f7c9b36d0860814f6de/pretzel-1.0.1.tar.gz" } ], "1.0.10": [ { "comment_text": "", "digests": { "md5": "ce78458c5ccd308ddc116f4798b7a038", "sha256": "11068545128627f6958f1f46f8bcc8108dd832aa66a4684d53e7d48b82dfe58b" }, "downloads": -1, "filename": "pretzel-1.0.10-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "ce78458c5ccd308ddc116f4798b7a038", "packagetype": "bdist_wheel", "python_version": "3.3", "requires_python": null, "size": 118814, "upload_time": "2013-08-05T19:51:56", "url": "https://files.pythonhosted.org/packages/5f/ad/beb4eefbe20deb4d19ef25757df3856c2f22cf33d2e61b6ae532f1034fff/pretzel-1.0.10-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6e715a7bf34168237d316c8f3f5b7bdd", "sha256": "5017fe30ce27291b9a2ecb45182c0d9bc78835dd1fe6b6b3f0d38324bc314f4e" }, "downloads": -1, "filename": "pretzel-1.0.10.tar.gz", "has_sig": false, "md5_digest": "6e715a7bf34168237d316c8f3f5b7bdd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 83840, "upload_time": "2013-08-05T19:51:00", "url": "https://files.pythonhosted.org/packages/41/9d/4a7be162a793b820948f7c6dba85ed9495cf91d1d3b530091dd6e6d73bf5/pretzel-1.0.10.tar.gz" } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "da46e6ab269341843f6ff51f837786a4", "sha256": "62fd3a805dbd2328da3ffc08802bb2a8d9c7089e14d79b5944b48114601b57e8" }, "downloads": -1, "filename": "pretzel-1.0.2.tar.gz", "has_sig": false, "md5_digest": "da46e6ab269341843f6ff51f837786a4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 68434, "upload_time": "2013-06-13T12:18:57", "url": "https://files.pythonhosted.org/packages/02/12/5b88ccd5102c0c286db6af2dbc4568abbe52205e1296d1e0188a3a1262f4/pretzel-1.0.2.tar.gz" } ], "1.0.3": [ { "comment_text": "", "digests": { "md5": "c8643000c1db914f41da6edce31eb455", "sha256": "9df0dc83332b0dae5876cdc955732022bad76ff9bedda0e3d0296554d476b89f" }, "downloads": -1, "filename": "pretzel-1.0.3.tar.gz", "has_sig": false, "md5_digest": "c8643000c1db914f41da6edce31eb455", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 70694, "upload_time": "2013-06-21T12:21:53", "url": "https://files.pythonhosted.org/packages/81/11/2884da79ae3c2144be0245d6b82ca217f72bd1bcedd68710a149840815c3/pretzel-1.0.3.tar.gz" } ], "1.0.4": [ { "comment_text": "", "digests": { "md5": "a693c756979c5651426973e0862f11f0", "sha256": "c85ec2962fa22bc52e4f2ebae2b30696b26d9f3f7759607fb8b14288cdd22039" }, "downloads": -1, "filename": "pretzel-1.0.4.tar.gz", "has_sig": false, "md5_digest": "a693c756979c5651426973e0862f11f0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 71326, "upload_time": "2013-06-26T11:21:34", "url": "https://files.pythonhosted.org/packages/c5/a1/9cbdfdeab83c2e283c7da776123f702c04908ddb91fcc7cbe04f2920e112/pretzel-1.0.4.tar.gz" } ], "1.0.5": [ { "comment_text": "", "digests": { "md5": "3d28c1360086dd766de043c43e8f1a33", "sha256": "2f2a2648249327e33952384c1b2e05e0f60686698805676cc171abc1fe05555c" }, "downloads": -1, "filename": "pretzel-1.0.5.tar.gz", "has_sig": false, "md5_digest": "3d28c1360086dd766de043c43e8f1a33", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 71337, "upload_time": "2013-06-30T08:54:17", "url": "https://files.pythonhosted.org/packages/b7/94/8cf26edc831bd13446557cca9b4cd5e0a1da01be777b77655f1e67e5b984/pretzel-1.0.5.tar.gz" } ], "1.0.6": [ { "comment_text": "", "digests": { "md5": "2039a4486bafaebaeb5202e46ae795eb", "sha256": "78d76e4be0c4d6f7943dfc931e3f755c4eceb871155458b46c5f4305a55c00ff" }, "downloads": -1, "filename": "pretzel-1.0.6.tar.gz", "has_sig": false, "md5_digest": "2039a4486bafaebaeb5202e46ae795eb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 76695, "upload_time": "2013-07-03T17:20:40", "url": "https://files.pythonhosted.org/packages/a1/6d/8f6110d9364ac45c3ed79a8fe040ed8594cc1d53773aa9f1f1b9138cab20/pretzel-1.0.6.tar.gz" } ], "1.0.7": [ { "comment_text": "", "digests": { "md5": "71c17a34455740773c3d23b3a6afe09f", "sha256": "aee80b12c46bf5929e27ff4c3c63deeb0bbf505675883144593b81e43ed49c53" }, "downloads": -1, "filename": "pretzel-1.0.7.tar.gz", "has_sig": false, "md5_digest": "71c17a34455740773c3d23b3a6afe09f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 81880, "upload_time": "2013-07-22T08:42:53", "url": "https://files.pythonhosted.org/packages/93/93/b415103582dadce849cd6dcf8afe55be1cdbf1af1e99eb5edd5eb1045fbf/pretzel-1.0.7.tar.gz" } ], "1.0.8": [ { "comment_text": "", "digests": { "md5": "bcf0e945f20b1126b950fd51d10bacd7", "sha256": "ff65c8a83b5bb444249947d05f07998725639bd50791465609e5e297bc5898ce" }, "downloads": -1, "filename": "pretzel-1.0.8.tar.gz", "has_sig": false, "md5_digest": "bcf0e945f20b1126b950fd51d10bacd7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 81989, "upload_time": "2013-07-22T09:22:50", "url": "https://files.pythonhosted.org/packages/60/29/c7e28739c77fee88b0997de3590ac265f893351075a9e67139d12ec97eac/pretzel-1.0.8.tar.gz" } ], "1.0.9": [ { "comment_text": "", "digests": { "md5": "34d83c193936a71e8af0eaf747738a30", "sha256": "e3d81ec63610e98d129da4c4e11193a6d7c4abb3b0e97c5476142089f26c5b57" }, "downloads": -1, "filename": "pretzel-1.0.9.tar.gz", "has_sig": false, "md5_digest": "34d83c193936a71e8af0eaf747738a30", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 82063, "upload_time": "2013-07-31T14:51:45", "url": "https://files.pythonhosted.org/packages/5a/9f/a54ccef6887e3feacc5216b864b39484b9e498d308d1330bdd07a7e5abc0/pretzel-1.0.9.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ce78458c5ccd308ddc116f4798b7a038", "sha256": "11068545128627f6958f1f46f8bcc8108dd832aa66a4684d53e7d48b82dfe58b" }, "downloads": -1, "filename": "pretzel-1.0.10-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "ce78458c5ccd308ddc116f4798b7a038", "packagetype": "bdist_wheel", "python_version": "3.3", "requires_python": null, "size": 118814, "upload_time": "2013-08-05T19:51:56", "url": "https://files.pythonhosted.org/packages/5f/ad/beb4eefbe20deb4d19ef25757df3856c2f22cf33d2e61b6ae532f1034fff/pretzel-1.0.10-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6e715a7bf34168237d316c8f3f5b7bdd", "sha256": "5017fe30ce27291b9a2ecb45182c0d9bc78835dd1fe6b6b3f0d38324bc314f4e" }, "downloads": -1, "filename": "pretzel-1.0.10.tar.gz", "has_sig": false, "md5_digest": "6e715a7bf34168237d316c8f3f5b7bdd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 83840, "upload_time": "2013-08-05T19:51:00", "url": "https://files.pythonhosted.org/packages/41/9d/4a7be162a793b820948f7c6dba85ed9495cf91d1d3b530091dd6e6d73bf5/pretzel-1.0.10.tar.gz" } ] }