{ "info": { "author": "Sander Voerman", "author_email": "sander@savoerman.nl", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.7" ], "description": "# Channels: Python iterator streams between coroutines\n\nThis package provides a simple and easy to use `Channel` class to send and\nreceive objects between coroutines.\n\nVersion 0.4, copyright © [Sander Voerman](sander@savoerman.nl), 2019.\n\n\n## Installation\n\nInstall the [sav.channels](https://pypi.org/project/sav.channels/)\npackage from the Python Package Index. See\n[installing packages](https://packaging.python.org/tutorials/installing-packages/)\nfor further instruction.\n\n## Overview\n\nA channel is a direct connection between two coroutines, through which they can\nsend and receive objects.\n\n### Simplex example\n\nEvery channel consists of a client and a server end. In the case where objects\nare sent in only one direction, the `client()` method returns the receiving\nend, which can be iterated over using `async for`. The `server` method returns\na context manager that can be opened and closed using `async with` in the\nproducing coroutine:\n\n```python\nimport asyncio\nfrom typing import AsyncIterator, AsyncGenerator\nfrom sav.channels import Channel\nfrom foo import Foo\n\nasync def produce(channel: Channel[Foo, None]) -> None:\n async with channel.server() as server:\n await server.asend(Foo(\"One\"))\n await produce_two(server)\n await server.asend(Foo(\"Three\"))\n\nasync def produce_two(server: AsyncGenerator[None, Foo]) -> None:\n await server.asend(Foo(\"Two\"))\n\nasync def consume(client: AsyncIterator[Foo]) -> None: \n async for foo in client:\n print(foo) \n\nasync def main() -> None:\n channel = Channel()\n await asyncio.gather(consume(channel.client()), produce(channel))\n\nasyncio.run(main())\n```\n\n### Duplex example\n\nThe objects returned by `channel.client()` and `async with channel.server()`\nare both [asynchronous generators](https://www.python.org/dev/peps/pep-0525/)\nwhich support communication in both directions. The following example\ndemonstrates how data flows through the channel in both directions:\n\n```python\nimport asyncio\nimport itertools\nfrom sav.channels import Channel\n\nasync def letters(channel: Channel[str, int]) -> None:\n async with channel.server() as s: # wait for the client\n print(await s.asend(\"A\")) # send and receive\n print(await s.asend(\"B\")) # send and receive\n\nasync def numbers(channel: Channel[str, int]) -> None:\n asend = channel.client().asend\n try:\n print(await asend(None)) # receive only\n for i in itertools.count():\n print(await asend(i)) # send and receive\n except StopAsyncIteration:\n pass\n\nasync def main() -> None:\n channel = Channel()\n await asyncio.gather(letters(channel), numbers(channel))\n\nasyncio.run(main())\n```\n\nThis will produce the result:\n\n```\nA\n0\nB\n1\n```\n\nHence, the first item to be sent through the channel is the one sent by the\nserver. The `async with` block starts the server by awaiting `asend(None)`,\nwhich blocks until the client is started and waiting for the first item to\nreceive. When execution flows off the `async with` block, the server is\nclosed by awaiting `aclose()`, which causes the waiting client to raise\n`StopAsyncIteration`.\n\n\n## The purpose of channels\n\nAlthough the possibility to send values in both directions can be useful in\ncertain situations, it is not what makes channels interesting, or why we need\nthem. The purpose of a channel lies in the fact that it reverses the\ndirections, so to speak, in which the client and server generators send and\nyield values.\n\n### Using a channel to push into a pipeline\n\nIf a value is sent into the server generator, it is yielded by\nthe client generator. Which means you can pass the client generator to your own\nasynchronous generator function, and have that function pull values out of\nthe channel, process them, and yield the results to another generator down\nyour processing pipeline. Nevertheless, every time the pipeline requests\nanother item from the channel, the producer coroutine that was awaiting the\nresult from `server.asend` resumes execution. This means that from the perspective\nof the producer, the pipeline looks like a *reverse* generator. Channels give\nyou the power of reverse generator pipelines without actually having to write\nreverse generator functions.\n\n### Refactoring generators into producers and vice-versa\n\nThe server context of a channel is designed to mirror the body of an\nasynchronous generator function:\n\n```python\n\nasync with chan.server() as s:\n a = await s.asend('One')\n b = await s.asend('Two')\n c = await s.asend('Three')\n\nasync def agen():\n a = yield 'One'\n b = yield 'Two'\n c = yield 'Three'\n\n```\n\nThis resemblance between channels and generator functions allows easy\nrefactoring. For example, when there is only one coroutine sending values\ninto a channel, and it does not do anything else besides that (as in the\nexample above), it should\nbe changed into an asynchronous generator function. On the other hand,\nthere are certain limitations that asynchronous generator functions have\nwhich can make them unwieldy. If more and more functions need to be turned\ninto generator functions because they need to `yield` to other generators,\nor if the code is full of `while True: f((yield))` loops instead of\n`async for x: f(x)` loops, refactoring generator functions into channels\nmay be desirable.\n\n### Pushing into multiple channels from a single routine\n\nChannels allow greater flexibility because you can send values\ninto different channels from within a single function or loop, whereas an\nasynchronous generator function shares the limitation with synchronous\ngenerators that it can only yield to the same generator object.\n\n### Efficient delegation to another producer\n\nAsynchronous generator functions do not support `yield from g` in order\nto delegate to another generator. Instead, you have to write\n`async for x in g: yield x` (in the simplex case) which means that the event\nloop has to jump in and out of the delegating generator every time\nbefore it jumps into the producing generator, since the semantics of your\ncode requires that the value of `x` be updated on every iteration.\nBy contrast, the object returned by a channel when you use `async with` may\nbe passed onward to delegate production to another coroutine, as shown in the\nfirst example at the top of this document.\n\n\n## Combining asynchronous and synchronous iteration\n\nThe `StreamChannel` class provides additional reading and writing methods\nthat allow sending multiple items, or even synchronous unsized iterators,\nthrough the channel without passing control back to the event loop for\nevery single item.\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/sandervoerman/channels", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "sav.channels", "package_url": "https://pypi.org/project/sav.channels/", "platform": "", "project_url": "https://pypi.org/project/sav.channels/", "project_urls": { "Homepage": "https://github.com/sandervoerman/channels" }, "release_url": "https://pypi.org/project/sav.channels/0.4/", "requires_dist": null, "requires_python": ">=3.7", "summary": "Iterable streams between coroutines", "version": "0.4" }, "last_serial": 4788918, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "244b5cf8ffe36cba97660f4626342811", "sha256": "7197f5e41995b45f09bab0a6f4d99a24a8520ea7ef41eaae685bd5872c7730f6" }, "downloads": -1, "filename": "sav.channels-0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "244b5cf8ffe36cba97660f4626342811", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 3914, "upload_time": "2018-11-05T02:04:56", "url": "https://files.pythonhosted.org/packages/3c/84/92a53975966c48a69d51befceefb86434e5f6b34b1a66baa67b77223c947/sav.channels-0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ab2e7bd5b84ec3632962c7f336532d28", "sha256": "c4f3433b5005874dce5053f42dd39e949423b1f98d2f6a8b46c6a7408c2c6e52" }, "downloads": -1, "filename": "sav.channels-0.1.tar.gz", "has_sig": false, "md5_digest": "ab2e7bd5b84ec3632962c7f336532d28", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 2902, "upload_time": "2018-11-05T02:04:57", "url": "https://files.pythonhosted.org/packages/ee/28/2466c50833184ba25b51b044169857fe47883a75fa78310ec02e78b6f1d3/sav.channels-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "3519e620b9f081af9a9278dbee708eaf", "sha256": "236eedf7a16fe68d7968a721784980567abd220b41db3d86d36728f26bc6c421" }, "downloads": -1, "filename": "sav.channels-0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "3519e620b9f081af9a9278dbee708eaf", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 5223, "upload_time": "2018-11-05T16:58:28", "url": "https://files.pythonhosted.org/packages/61/fb/1bc7a98b9bd0fe91f363ee973528123fc8bdb79319d311469a45c225cf66/sav.channels-0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e17772026abb505d246011159e3e775e", "sha256": "0d0d5d185c091dbc514a72445954d42dc052a2307b1bc559c5275ddf16aa7a88" }, "downloads": -1, "filename": "sav.channels-0.2.tar.gz", "has_sig": false, "md5_digest": "e17772026abb505d246011159e3e775e", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 3455, "upload_time": "2018-11-05T16:58:30", "url": "https://files.pythonhosted.org/packages/c2/ed/03f835911504a9a51472f62e62507f3861283b71e62e4e8949cff7396daf/sav.channels-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "84e07a284ba51ccd8f7e84b60caa607b", "sha256": "4af692deab652b2cac819c9d66fd465da26c155407cf039c822c443a996f13e9" }, "downloads": -1, "filename": "sav.channels-0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "84e07a284ba51ccd8f7e84b60caa607b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 5662, "upload_time": "2019-01-17T18:19:04", "url": "https://files.pythonhosted.org/packages/bd/1f/c99882c7009e60f0782eaf60aee1f8078b2a88ed737ed65eab2d8ee409ca/sav.channels-0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5050bc444ac2560cd8509499f8b7f00d", "sha256": "89a4303e339925112f826dd88babf72d62dc66b25c8c67465710e7543902c88d" }, "downloads": -1, "filename": "sav.channels-0.3.tar.gz", "has_sig": false, "md5_digest": "5050bc444ac2560cd8509499f8b7f00d", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 4766, "upload_time": "2019-01-17T18:19:07", "url": "https://files.pythonhosted.org/packages/fd/47/99ee2349160d0b925b9ea1ed8b3bba47b3f9e58ec3911aabb31e2fc839ff/sav.channels-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "793525573f88ec24db2810dc797a9492", "sha256": "22d0b213a29bb3b07b2fa2eaca81c33d99b3500b6a6dc49d0256322a74aaff5e" }, "downloads": -1, "filename": "sav.channels-0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "793525573f88ec24db2810dc797a9492", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 7110, "upload_time": "2019-02-06T23:15:29", "url": "https://files.pythonhosted.org/packages/a8/ff/73db9ec24063282577d6eee9dbef620cae5c310dd071d8c5f91391c1df77/sav.channels-0.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "348d3206d09f9caf9a9799eb137a0e86", "sha256": "6049e1d47bdc1a4f075ca079578ad6aaa36b65181784ff1b9c1c34d971e9b508" }, "downloads": -1, "filename": "sav.channels-0.4.tar.gz", "has_sig": false, "md5_digest": "348d3206d09f9caf9a9799eb137a0e86", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 5789, "upload_time": "2019-02-06T23:15:31", "url": "https://files.pythonhosted.org/packages/47/43/92e41047efc65d478ede6e22a388e090485e12db92648aa440ce2bd3d7e3/sav.channels-0.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "793525573f88ec24db2810dc797a9492", "sha256": "22d0b213a29bb3b07b2fa2eaca81c33d99b3500b6a6dc49d0256322a74aaff5e" }, "downloads": -1, "filename": "sav.channels-0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "793525573f88ec24db2810dc797a9492", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 7110, "upload_time": "2019-02-06T23:15:29", "url": "https://files.pythonhosted.org/packages/a8/ff/73db9ec24063282577d6eee9dbef620cae5c310dd071d8c5f91391c1df77/sav.channels-0.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "348d3206d09f9caf9a9799eb137a0e86", "sha256": "6049e1d47bdc1a4f075ca079578ad6aaa36b65181784ff1b9c1c34d971e9b508" }, "downloads": -1, "filename": "sav.channels-0.4.tar.gz", "has_sig": false, "md5_digest": "348d3206d09f9caf9a9799eb137a0e86", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 5789, "upload_time": "2019-02-06T23:15:31", "url": "https://files.pythonhosted.org/packages/47/43/92e41047efc65d478ede6e22a388e090485e12db92648aa440ce2bd3d7e3/sav.channels-0.4.tar.gz" } ] }