From 9094b19ed41dc831df2c0c0aa62059b26f9478d9 Mon Sep 17 00:00:00 2001 From: Yuriy Kuzma Date: Sat, 20 Apr 2019 11:42:24 -0500 Subject: [PATCH] Fix issue #112: 'Dynamically calling a fixture causes a runtime error' by failing over to a ThreadPoolExecutor in cases where the expected event loop is already running. Notably this happens when calling because it dynamically calls a fixture that needs to be setup on an event loop that's already running pytest async tests --- pytest_asyncio/plugin.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 9f4e10d7..12326c99 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -51,6 +51,25 @@ def pytest_pycollect_makeitem(collector, name, obj): @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup(fixturedef, request): """Adjust the event loop policy when an event loop is produced.""" + + def run_until_complete(loop, coro): + """ + Fail over to a ThreadPoolExecutor in case the event loop is already + running. Particularly, this occurs when calling a dynamic fixture + using `request.getfixturevalue(argname)` + """ + + try: + return loop.run_until_complete(coro) + except RuntimeError: + with concurrent.futures.ThreadPoolExecutor(1) as pool: + loop = asyncio.new_event_loop() + try: + pool.submit(asyncio.set_event_loop, loop).result() + return pool.submit(loop.run_until_complete, coro).result() + finally: + loop.close() + if isasyncgenfunction(fixturedef.func): # This is an async generator function. Wrap it accordingly. f = fixturedef.func @@ -94,7 +113,7 @@ async def async_finalizer(): request.addfinalizer(finalizer) - return loop.run_until_complete(setup()) + return run_until_complete(loop, setup()) fixturedef.func = wrapper @@ -116,7 +135,7 @@ async def setup(): res = await f(*args, **kwargs) return res - return loop.run_until_complete(setup()) + return run_until_complete(loop, setup()) fixturedef.func = wrapper