oremanj / greenback

Reenter an asyncio or Trio event loop from synchronous code
https://greenback.readthedocs.io/
Other
80 stars 2 forks source link

Provide an event loop wrapper #32

Open douglas-raillard-arm opened 6 months ago

douglas-raillard-arm commented 6 months ago

Feature request

In the context of migrating away from nest_asyncio, I ended up using greenback. In order to support cases where the user 1. executes code (including imports) in a pre-existing asyncio Task (jupyterlab notebook) and 2. with non-stdlib event loops (uvloop when using gunicorn), the least intrusive setup seemed to be creating an internal thread running an event loop that:

  1. The project has full control on (since it's not exposed directly)
  2. Has a custom event loop that overrides create_task() to to wrap all coroutines with:
    def _wrap_coro(coro):
    async def coro_f():
        await greenback.ensure_portal()
        return await coro
    return coro_f()

This way it can achieve a result similar to with_portal_run_tree() on asyncio, as all Tasks created by this event loop will be portaled appropriately.

If a turnkey high-level solution is in the scope of greenback, this makes it possible to provide an implementation of asyncio.run() that is re-entrant, achieving the same result as nest_asyncio (with an extra thread, but working for any asyncio event loop rather than just the stdlib implementations).

An implementation (including the re-entrant run() function) is available here: https://github.com/ARM-software/devlib/pull/683/files

Note: wrapping an event loop into another class requires some __getattribute__ tricks, as asyncio.AbstractEventLoop provides a default implementation of all (?) methods that just raise. This makes it impossible to use __getattr__ to delegate non-overridden calls to the wrapped loop, see our _GreenbackEventLoop implementation.