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.
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.
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.
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.
- time
- sleep
- threading
- queue
- atexit
- socket
- select
- poll
- select
- anything else
- dns
- os
- read
- write
- ssl
- subprocess
- signal
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()
You need to import any module which requires non-patched code before
importing aevent
.
Modules which are known to be affected:
- multiprocessing
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.
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
.