Package: asyncio-gevent
asyncio-geventasyncio-gevent makes asyncio and gevent compatible. It provides utilities forrunning asyncio on gevent (by using gevent as asyncio's event loop)running gevent on asyncio (by using asyncio as gevent's event loop, still work in progress)converting greenlets to asyncio futuresconverting futures to asyncio greenletswrapping blocking or spawning functions in coroutines which spawn a greenlet and wait for its completionwrapping coroutines in spawning functions which block until the future is resolvedasyncio-gevent is a fork and rewrite ofaiogeventandtulipcorefor modern python 3.InstallInstallasyncio-geventfrom pypi using your favourite package manager.# If you use poetrypoetryaddasyncio-gevent# If you use pippipinstallasyncio-geventUsageRunning asyncio on geventIn order to runasyncioongevent, we need to set the (default)EventLoopPolicyto useasyncio_gevent.EventLoopPolicy.importgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())asyncdefmain():awaitasyncio.sleep(1)print("done")asyncio.run(main())After setting the event loop policy, asyncio will use an event loop that uses greenlets for scheduling.Under the hood, it uses the default selector-based event loop in asyncio with the the gevent selector implementation.Alternatively, you can also manually set and use the event loop.importgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventloop=asyncio_gevent.EventLoop()asyncio.set_event_loop(loop)asyncdefmain():awaitasyncio.sleep(1)print("done")loop.run_until_complete(main())Running gevent on asyncioThis implementation is still work-in-progress. It may work for simple examples, but otherwise fail in unexpected ways.In order to rungeventonasyncio,geventneeds to be initialised to use the asyncio event loop. This is done by setting the environment variableGEVENT_LOOPtoasyncio_gevent.gevent_loop.GeventLoopand then starting python.GEVENT_LOOP=asyncio_gevent.gevent_loop.GeventLooppython3myscript.pygevent will now run on asyncio.Alternatively, you can also set the loop configuration setting, preferably right after importinggeventand before monkey patching.importgeventgevent.config.loop="asyncio_gevent.gevent_loop.GeventLoop"Converting greenlets to asyncio futuresUseasyncio_gevent.greenlet_to_futureto convert a greenlet to an asyncio future. The future yields once the greenlet has finished execution.# Preamble: Apply the gevent monkey patch and initialise the asyncio event loop policyimportgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())# Main exampleimportgeventdefblocking_function()->int:gevent.sleep(10)return42asyncdefmain()->None:greenlet=gevent.spawn(blocking_function)future=asyncio_gevent.greenlet_to_future(greenlet)result=awaitfutureasyncio.run(main())If the greenlet is already dead when the future is awaited/scheduled, then the future will resolve with the result or raise the exception thrown immediately.If the greenlet is not yet running, the greenlet will by default be started when the future is awaited/scheduled. This is to ensure a sensible default behaviour and prevent odd concurrency issues. To prevent this auto-starting, you can passautostart_greenlet=Falseas an argument togreenlet_to_future.When a greenlet is killed without a custom exception type, it will return aGreenletExitexception. In this instance, the future get cancelled. If a custom exception type is used, the future will raise the exception.If the future gets cancelled, then by default the greenlet is killed. To prevent the greenlet from getting killed, you can passautokill_greenlet=Falseas an argument togreenlet_to_future.Converting asyncio futures to greenletsUseasyncio_gevent.future_to_greenletto convert a future to a greenlet.# Preamble: Apply the gevent monkey patch and initialise the asyncio event loop policyimportgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())# Main exampleimportgeventasyncdefasync_function()->int:awaitasyncio.sleep(1)return42defmain()->None:future=async_function()greenlet=asyncio_gevent.future_to_greenlet(future)greenlet.start()greenlet.join()assertgreenlet.get()==42main()The greenlet returned by this function will not start automatically, so you need to callGreenlet.start()manually.Iffutureis a coroutine object, it will be scheduled as a task on theloopwhen the greenlet starts. If noloopargument has been passed, the running event loop will be used. If there is no running event loop, a new event loop will be started using the current event loop policy.If the future is not already scheduled, then it won't be scheduled for execution until the greenlet starts running. To prevent the future from being scheduled automatically, you can passautostart_future=Falseas an argument tofuture_to_greenlet.If the greenlet gets killed, the by default the future gets cancelled. To prevent this from happening and having the future return theGreenletExitobjct instead, you can passautocancel_future=Falseas an argument tofuture_to_greenlet.If the future gets cancelled, the greenlet gets killed and will return aGreenletExit. This default behaviour can be circumvented by passingautokill_greenlet=Falseand the greenlet will raise theCancelledErrorinstead.Wrapping blocking or spawning functions in coroutinesUseasyncio_gevent.sync_to_asyncto wrap a blocking function or a function that may spawn greenlets and wait for their completion in a coroutine.# Preamble: Apply the gevent monkey patch and initialise the asyncio event loop policyimportgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())# Main exampleimportgeventdefblocking_function(duration:float):gevent.sleep(duration)return42async_function=asyncio_gevent.sync_to_async(blocking_function)asyncio.run(async_function(1.0))The returned corountine function will execute the original function in a spawned greenlet and await it's completion.Under the hood, this is just a thin convenience wrapper aroundasyncio_gevent.greenlet_to_future.As a result,asyncio_gevent.sync_to_asyncaccepts the same arguments asasyncio_gevent.greenlet_to_futureto achieve the same behaviour.asyncio_gevent.sync_to_asynccan also be used as a decorator# Preamble: Apply the gevent monkey patch and initialise the asyncio event loop policyimportgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())# Main exampleimportgevent@asyncio_gevent.sync_to_async()deffn(duration:float):gevent.sleep(duration)return42asyncio.run(fn(1.0))Wrapping coroutines in spawning functionsUseasyncio_gevent.async_to_syncto wrap a coroutine function or in a blocking function that spawns a greenlet and waits until the coroutine has returned.# Preamble: Apply the gevent monkey patch and initialise the asyncio event loop policyimportgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())# Main exampleimportgeventasyncdefasync_function(duration:float):awaitasyncio.sleep(duration)return42blocking_function=asyncio_gevent.async_to_sync(async_function)blocking_function(1)The returned function will execute the coroutine on an existing running loop or a new event loop and await it's completion.Under the hood, this is just a thin convenience wrapper aroundasyncio_gevent.future_to_greenlet.As a result,asyncio_gevent.async_to_syncaccepts the same arguments asasyncio_gevent.future_to_greenletto achieve the same behaviour.asyncio_gevent.async_to_synccan also be used as a decorator.# Preamble: Apply the gevent monkey patch and initialise the asyncio event loop policyimportgevent.monkeygevent.monkey.patch_all()importasyncioimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())# Main exampleimportgevent@asyncio_gevent.async_to_syncasyncdeffn(duration:float):awaitasyncio.sleep(duration)return42fn(1)Known limitationsgevent.sleepgevent.sleepwill pause the current greenlet. As a result, this will, by default, result in the greenlet powering the asyncio loop to be put to sleep, so nothing will execute.This is typically not intended.The solution to this problem is to wrap any code that callsgevent.sleepinasyncio.sync_to_asyncor (equivalently)asyncio_gevent.greenlet_to_future(gevent.spawn(f)). This causes the function to be executed in another greenlet which prevents the main greenlet from getting blocked.Example:importgevent.monkeygevent.monkey.patch_all()importasyncioimportthreadingimportgeventimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())asyncdeff():print("f",1)awaitasyncio.sleep(1)print("f",2)defg():print("g",1)gevent.sleep(2)print("g",2)asyncdefmain():awaitasyncio.gather(f(),asyncio_gevent.sync_to_async(g)())# OR equivalently# await asyncio.gather(f(), asyncio_gevent.greenlet_to_future(gevent.spawn(g)))if__name__=="__main__":asyncio.run(main())The output will be (as expected):g 1
f 1
f 2
g 2Ifgevent.sleepis called inside an async function, then the async function needs to first be wrapped inasyncio.async_to_sync.importgevent.monkeygevent.monkey.patch_all()importasyncioimportgeventimportasyncio_geventasyncio.set_event_loop_policy(asyncio_gevent.EventLoopPolicy())asyncdeff():print("f",1)awaitasyncio.sleep(1)print("f",2)asyncdefg():print("g",1)awaitasyncio.sleep(1)gevent.sleep(1)print("g",2)asyncdefmain():awaitasyncio.gather(f(),asyncio_gevent.sync_to_async(asyncio_gevent.async_to_sync(g))())if__name__=="__main__":asyncio.run(main())The output will again be (as expected):g 1
f 1
f 2
g 2LicenseMIT