Skip to content

Commit 66b5bd0

Browse files
committed
feat: Tests using package or class loop scopes no longer raise an error when there's no surrounding package or class.
1 parent 9e377f9 commit 66b5bd0

File tree

5 files changed

+20
-69
lines changed

5 files changed

+20
-69
lines changed

changelog.d/+bd8f0ee6.changed.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The *loop_scope* argument to ``pytest.mark.asyncio`` no longer forces that a pytest Collector exists at the level of the specified scope. For example, a test function marked with ``pytest.mark.asyncio(loop_scope="class")`` no longer requires a class surrounding the test. This is consistent with the behavior of the *scope* argument to ``pytest_asyncio.fixture``.

docs/reference/markers/index.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,11 @@ The following code example provides a shared event loop for all tests in `TestCl
2727
.. include:: class_scoped_loop_strict_mode_example.py
2828
:code: python
2929

30-
If you request class scope for a test that is not part of a class, it will result in a *UsageError*.
3130
Similar to class-scoped event loops, a module-scoped loop is provided when setting mark's scope to *module:*
3231

3332
.. include:: module_scoped_loop_strict_mode_example.py
3433
:code: python
3534

36-
Package-scoped loops only work with `regular Python packages. <https://docs.python.org/3/glossary.html#term-regular-package>`__
37-
That means they require an *__init__.py* to be present.
38-
Package-scoped loops do not work in `namespace packages. <https://docs.python.org/3/glossary.html#term-namespace-package>`__
3935
Subpackages do not share the loop with their parent package.
4036

4137
Tests marked with *session* scope share the same event loop, even if the tests exist in different packages.

pytest_asyncio/plugin.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -687,29 +687,26 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
687687
if not marker:
688688
return
689689
default_loop_scope = _get_default_test_loop_scope(metafunc.config)
690-
scope = _get_marked_loop_scope(marker, default_loop_scope)
691-
if scope == "function":
690+
loop_scope = _get_marked_loop_scope(marker, default_loop_scope)
691+
if loop_scope == "function":
692692
return
693-
event_loop_node = _retrieve_scope_root(metafunc.definition, scope)
694-
event_loop_fixture_id = event_loop_node.stash.get(_event_loop_fixture_id, None)
695-
696-
if event_loop_fixture_id:
697-
# This specific fixture name may already be in metafunc.argnames, if this
698-
# test indirectly depends on the fixture. For example, this is the case
699-
# when the test depends on an async fixture, both of which share the same
700-
# event loop fixture mark.
701-
if event_loop_fixture_id in metafunc.fixturenames:
702-
return
703-
fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
704-
assert fixturemanager is not None
705-
# Add the scoped event loop fixture to Metafunc's list of fixture names and
706-
# fixturedefs and leave the actual parametrization to pytest
707-
# The fixture needs to be appended to avoid messing up the fixture evaluation
708-
# order
709-
metafunc.fixturenames.append(event_loop_fixture_id)
710-
metafunc._arg2fixturedefs[event_loop_fixture_id] = (
711-
fixturemanager._arg2fixturedefs[event_loop_fixture_id]
712-
)
693+
event_loop_fixture_id = f"_{loop_scope}_event_loop"
694+
# This specific fixture name may already be in metafunc.argnames, if this
695+
# test indirectly depends on the fixture. For example, this is the case
696+
# when the test depends on an async fixture, both of which share the same
697+
# event loop fixture mark.
698+
if event_loop_fixture_id in metafunc.fixturenames:
699+
return
700+
fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
701+
assert fixturemanager is not None
702+
# Add the scoped event loop fixture to Metafunc's list of fixture names and
703+
# fixturedefs and leave the actual parametrization to pytest
704+
# The fixture needs to be appended to avoid messing up the fixture evaluation
705+
# order
706+
metafunc.fixturenames.append(event_loop_fixture_id)
707+
metafunc._arg2fixturedefs[event_loop_fixture_id] = fixturemanager._arg2fixturedefs[
708+
event_loop_fixture_id
709+
]
713710

714711

715712
def _get_event_loop_no_warn(

tests/markers/test_class_scope.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,29 +82,6 @@ async def test_this_runs_in_same_loop(self):
8282
result.assert_outcomes(passed=2)
8383

8484

85-
def test_asyncio_mark_raises_when_class_scoped_is_request_without_class(
86-
pytester: pytest.Pytester,
87-
):
88-
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
89-
pytester.makepyfile(
90-
dedent(
91-
"""\
92-
import asyncio
93-
import pytest
94-
95-
@pytest.mark.asyncio(loop_scope="class")
96-
async def test_has_no_surrounding_class():
97-
pass
98-
"""
99-
)
100-
)
101-
result = pytester.runpytest("--asyncio-mode=strict")
102-
result.assert_outcomes(errors=1)
103-
result.stdout.fnmatch_lines(
104-
"*is marked to be run in an event loop with scope*",
105-
)
106-
107-
10885
def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
10986
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
11087
pytester.makepyfile(

tests/markers/test_package_scope.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -339,23 +339,3 @@ async def test_does_not_fail(sets_event_loop_to_none, n):
339339
)
340340
result = pytester.runpytest("--asyncio-mode=strict")
341341
result.assert_outcomes(passed=2)
342-
343-
344-
def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_being_set(
345-
pytester: Pytester,
346-
):
347-
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
348-
pytester.makepyfile(
349-
__init__="",
350-
test_module=dedent(
351-
"""\
352-
import pytest
353-
354-
@pytest.mark.asyncio(loop_scope="package")
355-
async def test_anything():
356-
pass
357-
"""
358-
),
359-
)
360-
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
361-
result.assert_outcomes(warnings=0, passed=1)

0 commit comments

Comments
 (0)