Metadata-Version: 2.1
Name: coroutines
Version: 0.3.0
Summary: Coroutines without an event loop
Project-URL: Homepage, https://github.com/ntessore/coroutines
Project-URL: Issues, https://github.com/ntessore/coroutines/issues
Author-email: Nicolas Tessore <n.tessore@ucl.ac.uk>
License-Expression: MIT
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.7
Provides-Extra: test
Requires-Dist: pytest>=6.0; extra == 'test'
Description-Content-Type: text/x-rst

``coroutines`` — Python coroutines without an event loop
========================================================

.. image:: https://github.com/ntessore/coroutines/actions/workflows/test.yml/badge.svg
 :target: https://github.com/ntessore/coroutines/actions/workflows/test.yml

.. image:: https://codecov.io/gh/ntessore/coroutines/graph/badge.svg?token=A6L220NL3Y
 :target: https://codecov.io/gh/ntessore/coroutines

This is a small package which provides tools to run ``async`` functions and
generators without an ``asyncio`` event loop.

The ``coroutines`` module provides functions such as |coroutines.run|_,
|coroutines.gather|_, and |coroutines.sleep|_ that work just like their
``asyncio`` counterparts, but without scheduling any tasks in an external event
loop. For example, the following code was adapted `from the asyncio
documentation`__:

__ https://docs.python.org/3.12/library/asyncio-task.html#asyncio.gather

.. code:: python

    import coroutines

    async def factorial(name, number):
        f = 1
        for i in range(2, number + 1):
            print(f"Task {name}: Compute factorial({number}), currently i={i}...")
            await coroutines.sleep()  # CHANGED: no argument
            f *= i
        print(f"Task {name}: factorial({number}) = {f}")
        return f

    async def main():
        # Schedule three calls *concurrently*:
        L = await coroutines.gather(
            factorial("A", 2),
            factorial("B", 3),
            factorial("C", 4),
        )
        print(L)

    coroutines.run(main())

    # Expected output:
    #
    #     Task A: Compute factorial(2), currently i=2...
    #     Task B: Compute factorial(3), currently i=2...
    #     Task C: Compute factorial(4), currently i=2...
    #     Task A: factorial(2) = 2
    #     Task B: Compute factorial(3), currently i=3...
    #     Task C: Compute factorial(4), currently i=3...
    #     Task B: factorial(3) = 6
    #     Task C: Compute factorial(4), currently i=4...
    #     Task C: factorial(4) = 24
    #     [2, 6, 24]

The example produces the same result as the ``asyncio`` code by simply calling,
suspending, and resuming the coroutines until they are completed. Practically,
the only difference between the ``coroutines`` and ``asyncio`` examples is that
|coroutines.sleep|_ does not take an argument. Because there is no external
event loop, a call to |coroutines.sleep|_ cannot suspend the current chain of
coroutines for a fixed amount of time, but only until it is resumed at the next
iteration.


Running coroutines
------------------

.. _coroutines.run:
.. parsed-literal::

   coroutines.\ **run**\ (*coro*) `# <coroutines.run_>`_

.. |coroutines.run| replace:: ``coroutines.run()``

Run a coroutine from synchronous code.


Suspending coroutines
---------------------

.. _coroutines.sleep:
.. parsed-literal::

   *awaitable* coroutines.\ **sleep**\ () `# <coroutines.sleep_>`_

.. |coroutines.sleep| replace:: ``coroutines.sleep()``

Suspend the current chain of coroutines, allowing another coroutine to run
concurrently.


Concurrent execution
--------------------

.. _coroutines.gather:
.. parsed-literal::

   *awaitable* coroutines.\ **gather**\ (*\*aws*) `# <coroutines.gather_>`_

.. |coroutines.gather| replace:: ``coroutines.gather()``

Run the given awaitables *aws* concurrently.

Returns a coroutine that loops over *aws*, resuming each awaitable in
turn until it is suspended again or finished.  Execution is suspended
after each pass over *aws*, so that other coroutines can run while the
result of ``gather()`` is being awaited.

The result of awaiting ``gather()`` is the aggregate list of awaited results
from *aws* in the same order.


Creating awaitables
-------------------

The ``coroutines`` module contains a number of helper functions that turn
regular objects into awaitable variants of themselves.

.. _coroutines.awaitable:
.. parsed-literal::

   *awaitable* coroutines.\ **awaitable**\ (*obj=None*) `# <coroutines.awaitable_>`_

.. |coroutines.awaitable| replace:: ``coroutines.awaitable()``

Create an awaitable variant of *obj*.  Returns a coroutine that awaits
|coroutines.sleep|_ before returning *obj*.


.. _coroutines.aiterable:
.. parsed-literal::

   *awaitable* coroutines.\ **aiterable**\ (*iterable*) `# <coroutines.aiterable_>`_

.. |coroutines.aiterable| replace:: ``coroutines.aiterable()``

Create an awaitable variant of an iterable.  Returns an asynchronous generator
that awaits |coroutines.sleep|_ before each item in *iterable*.


.. _coroutines.arange:
.. parsed-literal::

   *awaitable* coroutines.\ **arange**\ (*stop*) `# <coroutines.arange_>`_
   *awaitable* coroutines.\ **arange**\ (*start, stop[, step]*)

.. |coroutines.arange| replace:: ``coroutines.arange()``

Create an awaitable variant of |range|_.  Returns an asynchronous generator
that awaits |coroutines.sleep|_ before each number.


.. |range| replace:: ``range()``
.. _range: https://docs.python.org/3/library/stdtypes.html#range
