Skip to content

Commit 5ff08a0

Browse files
committed
Reinstate event loop if unset
1 parent 2c75c45 commit 5ff08a0

File tree

4 files changed

+99
-1
lines changed

4 files changed

+99
-1
lines changed

changelog.d/1172.added.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Pyright type checking support in tox configuration to improve type safety and compatibility.

changelog.d/1177.fixed.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``RuntimeError: There is no current event loop in thread 'MainThread'`` when using shared event loops after any test unsets the event loop (such as when using ``asyncio.run`` and ``asyncio.Runner``).

pytest_asyncio/plugin.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import inspect
1111
import socket
1212
import sys
13+
import threading
1314
import traceback
1415
import warnings
1516
from asyncio import AbstractEventLoop, AbstractEventLoopPolicy
@@ -602,6 +603,13 @@ def _set_event_loop(loop: AbstractEventLoop | None) -> None:
602603
asyncio.set_event_loop(loop)
603604

604605

606+
def _reinstate_event_loop_on_main_thread() -> None:
607+
if threading.current_thread() is threading.main_thread():
608+
policy = _get_event_loop_policy()
609+
loop = _get_event_loop_no_warn(policy)
610+
policy.set_event_loop(loop)
611+
612+
605613
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
606614
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
607615
"""
@@ -663,7 +671,12 @@ def wrap_in_sync(
663671
@functools.wraps(func)
664672
def inner(*args, **kwargs):
665673
coro = func(*args, **kwargs)
666-
_loop = _get_event_loop_no_warn()
674+
try:
675+
_loop = _get_event_loop_no_warn()
676+
except RuntimeError:
677+
# Handle situation where asyncio.set_event_loop(None) removes shared loops.
678+
_reinstate_event_loop_on_main_thread()
679+
_loop = _get_event_loop_no_warn()
667680
task = asyncio.ensure_future(coro, loop=_loop)
668681
try:
669682
_loop.run_until_complete(task)

tests/test_set_event_loop.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from textwrap import dedent
4+
5+
import pytest
6+
from pytest import Pytester
7+
8+
9+
@pytest.mark.parametrize("test_loop_scope", ("function", "module", "session"))
10+
def test_set_event_loop_none(pytester: Pytester, test_loop_scope: str):
11+
pytester.makeini(
12+
dedent(
13+
f"""\
14+
[pytest]
15+
asyncio_default_test_loop_scope = {test_loop_scope}
16+
asyncio_default_fixture_loop_scope = function
17+
"""
18+
)
19+
)
20+
pytester.makepyfile(
21+
dedent(
22+
"""\
23+
import asyncio
24+
import pytest
25+
26+
pytest_plugins = "pytest_asyncio"
27+
28+
@pytest.mark.asyncio
29+
async def test_before():
30+
pass
31+
32+
33+
def test_set_event_loop_none():
34+
asyncio.set_event_loop(None)
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_after():
39+
pass
40+
"""
41+
)
42+
)
43+
result = pytester.runpytest_subprocess()
44+
result.assert_outcomes(passed=3)
45+
46+
47+
def test_set_event_loop_none_class(pytester: Pytester):
48+
pytester.makeini(
49+
dedent(
50+
"""\
51+
[pytest]
52+
asyncio_default_test_loop_scope = class
53+
asyncio_default_fixture_loop_scope = function
54+
"""
55+
)
56+
)
57+
pytester.makepyfile(
58+
dedent(
59+
"""\
60+
import asyncio
61+
import pytest
62+
63+
pytest_plugins = "pytest_asyncio"
64+
65+
66+
class TestClass:
67+
@pytest.mark.asyncio
68+
async def test_before(self):
69+
pass
70+
71+
72+
def test_set_event_loop_none(self):
73+
asyncio.set_event_loop(None)
74+
75+
76+
@pytest.mark.asyncio
77+
async def test_after(self):
78+
pass
79+
"""
80+
)
81+
)
82+
result = pytester.runpytest_subprocess()
83+
result.assert_outcomes(passed=3)

0 commit comments

Comments
 (0)