Metadata-Version: 2.1
Name: sav.channels
Version: 0.3
Summary: Iterable streams between coroutines
Home-page: https://github.com/sandervoerman/channels
Author: Sander Voerman
Author-email: sander@savoerman.nl
License: UNKNOWN
Description: # Channels: Python iterator streams between coroutines
        
        This package provides a simple and easy to use `Channel` class to stream objects
        between coroutines.
        
        Version 0.3, copyright &copy; [Sander Voerman](sander@savoerman.nl), 2019.
        
        
        ## Installation
        
        Install the [sav.channels](https://pypi.org/project/sav.channels/)
        package from the Python Package Index. See
        [installing packages](https://packaging.python.org/tutorials/installing-packages/)
        for further instruction.
        
        ## Overview
        
        A channel is a direct connection between two coroutines, through which they can
        send and receive objects.
        
        ### Simplex example
        
        In the case where objects are sent in only one direction, the channel may be
        opened using `async with` in the producing coroutine and `async for` in the
        consuming coroutine:
        
        ```python
        import asyncio
        from typing import AsyncGenerator
        from sav.channels import Channel
        from foo import Foo
        
        async def produce(channel: Channel[Foo, None]) -> None:
            async with channel as server:
                await server.asend(Foo("One"))
                await produce_two(server)
                await server.asend(Foo("Three"))
        
        async def produce_two(server: AsyncGenerator[None, Foo]) -> None:
            await server.asend(Foo("Two"))
        
        async def consume(channel: Channel[Foo, None]) -> None:    
            async for foo in channel:
                print(foo)      
        
        async def main() -> None:
            channel = Channel()
            await asyncio.gather(consume(channel), produce(channel))
        
        asyncio.run(main())
        ```
        
        ### Duplex example
        
        The objects returned by `channel` when `async for` and `async with` invoke
        the `__aiter__` and `__aenter__` methods are also accessible as the
        instance attributes `channel.client` and `channel.server`, respectively. Both
        objects are
        [asynchronous generators](https://www.python.org/dev/peps/pep-0525/).
        The following example demonstrates how data flows through the channel in both
        directions:
        
        ```python
        import asyncio
        import itertools
        from sav.channels import Channel
        
        async def letters(channel: Channel[str, int]) -> None:
            asend = channel.server.asend
            async with channel:            # wait for the client
                print(await asend("A"))    # send and receive
                print(await asend("B"))    # send and receive
        
        async def numbers(channel: Channel[str, int]) -> None:
            asend = channel.client.asend
            try:
                print(await asend(None))   # receive only
                for i in itertools.count():
                    print(await asend(i))  # send and receive
            except StopAsyncIteration:
                pass
        
        async def main() -> None:
            channel = Channel()
            await asyncio.gather(letters(channel), numbers(channel))
        
        asyncio.run(main())
        ```
        
        This will produce the result:
        
        ```
        A
        0
        B
        1
        ```
        
        Hence, the first item to be sent through the channel is the one sent by the
        server. The `async with` block starts the server by awaiting `asend(None)`,
        which blocks until the client is started and waiting for the first item to
        receive. When execution flows off the `async with` block, the server is
        closed by awaiting `aclose()`, which causes the waiting client to raise
        `StopAsyncIteration`.
        
        
        ## The purpose of channels
        
        Although the possibility to send values in both directions can be useful in
        certain situations, it is not what makes channels interesting, or why we need
        them. The purpose of a channel lies in the fact that it reverses the
        directions, so to speak, in which the client and server generators send and
        yield values.
        
        ### Using a channel to push into a pipeline
        
        If a value is sent into the server generator, it is yielded by
        the client generator. Which means you can pass the client generator to your own
        asynchronous generator function, and have that function pull values out of
        the channel, process them, and yield the results to another generator down
        your processing pipeline. Nevertheless, every time the pipeline requests
        another item from the channel, the producer coroutine that was awaiting the
        result from `server.asend` resumes execution. This means that from the perspective
        of the producer, the pipeline looks like a *reverse* generator. Channels give
        you the power of reverse generator pipelines without actually having to write
        reverse generator functions.
        
        ### Refactoring generators into producers and vice-versa
        
        The server context of a channel is designed to mirror the body of an
        asynchronous generator function:
        
        ```python
        
        async with chan as s:
            a = await s.asend('One')
            b = await s.asend('Two')
            c = await s.asend('Three')
        
        async def agen():
            a = yield 'One'
            b = yield 'Two'
            c = yield 'Three'
        
        ```
        
        This resemblance between channels and generator functions allows easy
        refactoring. For example, when there is only one coroutine sending values
        into a channel, and it does not do anything else besides that (as in the
        example above), it should
        be changed into an asynchronous generator function. On the other hand,
        there are certain limitations that asynchronous generator functions have
        which can make them unwieldy. If more and more functions need to be turned
        into generator functions because they need to `yield` to other generators,
        or if the code is full of `while True: f((yield))` loops instead of
        `async for x: f(x)` loops, refactoring generator functions into channels
        may be desirable.
        
        ### Pushing into multiple channels from a single routine
        
        Channels allow greater flexibility because you can send values
        into different channels from within a single function or loop, whereas an
        asynchronous generator function shares the limitation with synchronous
        generators that it can only yield to the same generator object.
        
        ### Efficient delegation to another producer
        
        Asynchronous generator functions do not support `yield from g` in order
        to delegate to another generator. Instead, you have to write
        `async for x in g: yield x` (in the simplex case) which means that the event
        loop has to jump in and out of the delegating generator every time
        before it jumps into the producing generator, since the semantics of your
        code requires that the value of `x` be updated on every iteration.
        By contrast, the object returned by a channel when you use `async with` may
        be passed onward to delegate production to another coroutine, as shown in the
        first example at the top of this document.
        
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3.7
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
