====================================
``aevent``: an asyncronizing library
====================================

``aevent`` lets you call boring synchronous Python from async code.
Without blocking or splatting ``async`` and ``await`` onto it.
Ideally, this works without modifying the synchronous code.

Put another way,
``aevent`` is to ``gevent`` what ``anyio`` is to ``greenlet``.

That is, it replaces standard Python functions with calls to `anyio`_
instead of ``gevent``.

Some limitations apply.

Usage
=====

**Before any other imports**, insert this code block into your main code::

   import aevent
   aevent.setup('trio')  # or asyncio, if you must
   
This will annoy various code checkers, but that can't be helped.

**Start your main loop** using ``aevent.run``, or call ``await aevent.per_task()``
in the task(s) that need to use patched code.

The ``aevent.native`` and ``aevent.patched`` context managers can be used to
temporarily disable or re-enable ``aevent``'s patches.


Support functions
-----------------

``aevent`` monkey-patches ``anyio``'s ``TaskGroup.spawn`` in two ways.

* the child task is instrumented to support `greenback`.

* ``spawn`` returns a cancel scope. You can use it to cancel the new task.

Call ``aevent.per_task`` in your child task if you start tasks some other way.


Threading
---------

Threads are translated to tasks. In order for that to work, you must start
your program with `aevent.run`, or run the sync code in question within an
`aevent.runner` async context manager. Runners may be nested.


Supported modules
=================

* time

  * sleep

* threading
* queue
* atexit
* socket
* select

  * poll

Not yet supported
-----------------

* select

  * anything else

* dns
* os

  * read

  * write

* ssl
* subprocess
* signal


Subclassing patched classes
---------------------------

Directly subclassing one of the classes patched by ``aevent`` does not
work and requires special consideration. Consider this code::

   class my_thread(threading.Thread):
      def run(self):
          ...

For use with ``aevent`` you can choose the original ``Thread``
implementation::

    orig_Thread = getattr(threading.Thread, "_aevent_orig", threading.Thread)
    class my_thread(orig_Thread):
      ...

or the ``aevent``-ified version::

    new_Thread = threading.Thread._aevent_new # fails when aevent is not loaded
    class my_thread(new_Thread):
      ...

or you might want to create two separate implementations, and switch based
on the aevent context::

    class _orig_my_thread(threading.Thread._aevent_orig):
       ...
    class _new_my_thread(threading.Thread._aevent_new):
       ...
    my_thread = aevent.patch__new_my_thread, name="my_thread", orig=_orig_my_thread)

If you generate local subclasses on the fly, you can simplify this to::

    def some_code():
        class my_thread(threading.Thread._aevent_select()):
            def run(self):
                ...
        job = my_tread()
        my_thread.start()


Other affected modules
----------------------

You need to import any module which requires non-patched code before
importing ``aevent``.

Modules which are known to be affected:

* multiprocessing


Internals
=========

``aevent``'s monkey patching is done mainly on the module/class level.
``gevent`` prefers to patch individual methods. This may cause some
reduced compatibility compared to ``gevent``.

``aevent`` works by prepending its local ``_monkey`` directory to the import path.
These modules try to afford the same public interface as the ones they're
replacing while calling the corresponding ``anyio`` functions through
`greenback_`.

Context switching back to async-flavored code is done by way of `greenback`_.

``aevent`` runs on Python 3.7 ff.

Testing
-------

The test suite runs with `trio`_ as backend. Due to ``aevent``'s monkeypatching,
switching backends around is not supported. However, you can set the
environment variable ``AEVENT_BACKEND`` to `asyncio`_ to run the test
suite with that.

The test suite pulls in a copy of `pyroute2`_ (no changes, other than fixing
bugs unrelated to ``aevent``) and tests against its test suite, thereby
(mostly) ensuring that this particular package works with ``aevent``.

.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _trio: https://github.com/python-trio/trio
.. _anyio: https://github.com/agronholm/anyio
.. _greenback: https://github.com/oremanj/greenback
.. _pyroute2: https://github.com/svinota/pyroute2