Skip to content

Fix error when a shared event loop is unset #1180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1172.added.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pyright type checking support in tox configuration to improve type safety and compatibility.
1 change: 1 addition & 0 deletions changelog.d/1177.fixed.rst
Original file line number Diff line number Diff line change
@@ -0,0 +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``).
14 changes: 13 additions & 1 deletion pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import inspect
import socket
import sys
import threading
import traceback
import warnings
from asyncio import AbstractEventLoop, AbstractEventLoopPolicy
Expand Down Expand Up @@ -602,6 +603,12 @@ def _set_event_loop(loop: AbstractEventLoop | None) -> None:
asyncio.set_event_loop(loop)


def _reinstate_event_loop_on_main_thread() -> None:
if threading.current_thread() is threading.main_thread():
policy = _get_event_loop_policy()
policy.set_event_loop(policy.new_event_loop())


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
"""
Expand Down Expand Up @@ -663,7 +670,12 @@ def wrap_in_sync(
@functools.wraps(func)
def inner(*args, **kwargs):
coro = func(*args, **kwargs)
_loop = _get_event_loop_no_warn()
try:
_loop = _get_event_loop_no_warn()
except RuntimeError:
# Handle situation where asyncio.set_event_loop(None) removes shared loops.
_reinstate_event_loop_on_main_thread()
_loop = _get_event_loop_no_warn()
task = asyncio.ensure_future(coro, loop=_loop)
try:
_loop.run_until_complete(task)
Expand Down
83 changes: 83 additions & 0 deletions tests/test_set_event_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from __future__ import annotations

from textwrap import dedent

import pytest
from pytest import Pytester


@pytest.mark.parametrize("test_loop_scope", ("function", "module", "session"))
def test_set_event_loop_none(pytester: Pytester, test_loop_scope: str):
pytester.makeini(
dedent(
f"""\
[pytest]
asyncio_default_test_loop_scope = {test_loop_scope}
asyncio_default_fixture_loop_scope = function
"""
)
)
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"

@pytest.mark.asyncio
async def test_before():
pass


def test_set_event_loop_none():
asyncio.set_event_loop(None)


@pytest.mark.asyncio
async def test_after():
pass
"""
)
)
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)


def test_set_event_loop_none_class(pytester: Pytester):
pytester.makeini(
dedent(
"""\
[pytest]
asyncio_default_test_loop_scope = class
asyncio_default_fixture_loop_scope = function
"""
)
)
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest

pytest_plugins = "pytest_asyncio"


class TestClass:
@pytest.mark.asyncio
async def test_before(self):
pass


def test_set_event_loop_none(self):
asyncio.set_event_loop(None)


@pytest.mark.asyncio
async def test_after(self):
pass
"""
)
)
result = pytester.runpytest_subprocess()
result.assert_outcomes(passed=3)
Loading