Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a327200

Browse files
committedNov 5, 2023
[feat!] Replaced the asyncio_event_loop marker with an optional "scope" kwarg to the asyncio mark.
Signed-off-by: Michael Seifert <[email protected]>
1 parent 2cd541e commit a327200

17 files changed

+232
-263
lines changed
 

‎docs/source/reference/changelog.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@
22
Changelog
33
=========
44

5+
0.23.0 (UNRELEASED)
6+
===================
7+
This release is backwards-compatible with v0.21.
8+
Changes are non-breaking, unless you upgrade from v0.22.
9+
10+
BREAKING: The *asyncio_event_loop* mark has been removed. Class-scoped and module-scoped event loops can be requested
11+
via the *scope* keyword argument to the _asyncio_ mark.
12+
513
0.22.0 (2023-10-31)
614
===================
15+
This release has been yanked from PyPI due to fundamental issues with the _asyncio_event_loop_ mark.
16+
717
- Class-scoped and module-scoped event loops can be requested
818
via the _asyncio_event_loop_ mark. `#620 <https://github.com/pytest-dev/pytest-asyncio/pull/620>`_
919
- Deprecate redefinition of the `event_loop` fixture. `#587 <https://github.com/pytest-dev/pytest-asyncio/issues/531>`_
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import asyncio
2+
3+
import pytest
4+
5+
6+
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
7+
pass
8+
9+
10+
@pytest.fixture(scope="module")
11+
def event_loop_policy(request):
12+
return CustomEventLoopPolicy()
13+
14+
15+
@pytest.mark.asyncio(scope="module")
16+
async def test_uses_custom_event_loop_policy():
17+
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import asyncio
2+
from asyncio import DefaultEventLoopPolicy
3+
4+
import pytest
5+
6+
7+
class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
8+
pass
9+
10+
11+
@pytest.fixture(
12+
params=(
13+
DefaultEventLoopPolicy(),
14+
CustomEventLoopPolicy(),
15+
),
16+
)
17+
def event_loop_policy(request):
18+
return request.param
19+
20+
21+
@pytest.mark.asyncio
22+
async def test_uses_custom_event_loop_policy():
23+
assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy)

‎docs/source/reference/fixtures/index.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ If you need to change the type of the event loop, prefer setting a custom event
2222
If the ``pytest.mark.asyncio`` decorator is applied to a test function, the ``event_loop``
2323
fixture will be requested automatically by the test function.
2424

25+
event_loop_policy
26+
=================
27+
Returns the event loop policy used to create asyncio event loops.
28+
The default return value is *asyncio.get_event_loop_policy().*
29+
30+
This fixture can be overridden when a different event loop policy should be used.
31+
32+
.. include:: event_loop_policy_example.py
33+
:code: python
34+
35+
Multiple policies can be provided via fixture parameters.
36+
All tests to which the fixture is applied are run once for each fixture parameter.
37+
The following example runs the test with different event loop policies.
38+
39+
.. include:: event_loop_policy_parametrized_example.py
40+
:code: python
41+
2542
unused_tcp_port
2643
===============
2744
Finds and yields a single unused TCP port on the localhost interface. Useful for

‎docs/source/reference/markers/class_scoped_loop_auto_mode_example.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

‎docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

‎docs/source/reference/markers/class_scoped_loop_strict_mode_example.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
import pytest
44

55

6-
@pytest.mark.asyncio_event_loop
6+
@pytest.mark.asyncio(scope="class")
77
class TestClassScopedLoop:
88
loop: asyncio.AbstractEventLoop
99

10-
@pytest.mark.asyncio
1110
async def test_remember_loop(self):
1211
TestClassScopedLoop.loop = asyncio.get_running_loop()
1312

14-
@pytest.mark.asyncio
1513
async def test_this_runs_in_same_loop(self):
1614
assert asyncio.get_running_loop() is TestClassScopedLoop.loop

‎docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
import pytest_asyncio
66

77

8-
@pytest.mark.asyncio_event_loop
8+
@pytest.mark.asyncio(scope="class")
99
class TestClassScopedLoop:
1010
loop: asyncio.AbstractEventLoop
1111

1212
@pytest_asyncio.fixture
1313
async def my_fixture(self):
1414
TestClassScopedLoop.loop = asyncio.get_running_loop()
1515

16-
@pytest.mark.asyncio
1716
async def test_runs_is_same_loop_as_fixture(self, my_fixture):
1817
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import asyncio
2+
3+
import pytest
4+
5+
# Marks all test coroutines in this module
6+
pytestmark = pytest.mark.asyncio
7+
8+
9+
async def test_runs_in_asyncio_event_loop():
10+
assert asyncio.get_running_loop()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import asyncio
2+
3+
import pytest
4+
5+
6+
@pytest.mark.asyncio
7+
async def test_runs_in_asyncio_event_loop():
8+
assert asyncio.get_running_loop()

‎docs/source/reference/markers/index.rst

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,33 @@ Markers
44

55
``pytest.mark.asyncio``
66
=======================
7-
A coroutine or async generator with this marker will be treated as a test function by pytest. The marked function will be executed as an
8-
asyncio task in the event loop provided by the ``event_loop`` fixture.
7+
A coroutine or async generator with this marker is treated as a test function by pytest.
8+
The marked function is executed as an asyncio task in the event loop provided by pytest-asyncio.
99

10-
In order to make your test code a little more concise, the pytest |pytestmark|_
11-
feature can be used to mark entire modules or classes with this marker.
12-
Only test coroutines will be affected (by default, coroutines prefixed by
13-
``test_``), so, for example, fixtures are safe to define.
14-
15-
.. include:: pytestmark_asyncio_strict_mode_example.py
10+
.. include:: function_scoped_loop_strict_mode_example.py
1611
:code: python
1712

18-
In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted, the marker is added
19-
automatically to *async* test functions.
20-
21-
22-
``pytest.mark.asyncio_event_loop``
23-
==================================
24-
Test classes or modules with this mark provide a class-scoped or module-scoped asyncio event loop.
25-
26-
This functionality is orthogonal to the `asyncio` mark.
27-
That means the presence of this mark does not imply that async test functions inside the class or module are collected by pytest-asyncio.
28-
The collection happens automatically in `auto` mode.
29-
However, if you're using strict mode, you still have to apply the `asyncio` mark to your async test functions.
13+
Multiple async tests in a single class or module can be marked using |pytestmark|_.
3014

31-
The following code example uses the `asyncio_event_loop` mark to provide a shared event loop for all tests in `TestClassScopedLoop`:
32-
33-
.. include:: class_scoped_loop_strict_mode_example.py
15+
.. include:: function_scoped_loop_pytestmark_strict_mode_example.py
3416
:code: python
3517

36-
In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted:
37-
38-
.. include:: class_scoped_loop_auto_mode_example.py
39-
:code: python
18+
The ``pytest.mark.asyncio`` marker can be omitted entirely in *auto* mode, where the *asyncio* marker is added automatically to *async* test functions.
4019

41-
Similarly, a module-scoped loop is provided when adding the `asyncio_event_loop` mark to the module:
20+
By default, each test runs in it's own asyncio event loop.
21+
Multiple tests can share the same event loop by providing a *scope* keyword argument to the *asyncio* mark.
22+
The following code example provides a shared event loop for all tests in `TestClassScopedLoop`:
4223

43-
.. include:: module_scoped_loop_auto_mode_example.py
44-
:code: python
45-
46-
The `asyncio_event_loop` mark supports an optional `policy` keyword argument to set the asyncio event loop policy.
47-
48-
.. include:: class_scoped_loop_custom_policy_strict_mode_example.py
24+
.. include:: class_scoped_loop_strict_mode_example.py
4925
:code: python
5026

5127

52-
The ``policy`` keyword argument may also take an iterable of event loop policies. This causes tests under by the `asyncio_event_loop` mark to be parametrized with different policies:
28+
Similarly, a module-scoped loop is provided when setting mark's scope to *module:*
5329

54-
.. include:: class_scoped_loop_custom_policies_strict_mode_example.py
30+
.. include:: module_scoped_loop_strict_mode_example.py
5531
:code: python
5632

57-
If no explicit policy is provided, the mark will use the loop policy returned by ``asyncio.get_event_loop_policy()``.
58-
59-
Fixtures and tests sharing the same `asyncio_event_loop` mark are executed in the same event loop:
60-
61-
.. include:: class_scoped_loop_with_fixture_strict_mode_example.py
62-
:code: python
33+
The supported scopes are *class*, and *module.*
6334

6435
.. |pytestmark| replace:: ``pytestmark``
6536
.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules

‎docs/source/reference/markers/module_scoped_loop_auto_mode_example.py renamed to ‎docs/source/reference/markers/module_scoped_loop_strict_mode_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
pytestmark = pytest.mark.asyncio_event_loop
5+
pytestmark = pytest.mark.asyncio(scope="module")
66

77
loop: asyncio.AbstractEventLoop
88

‎docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎pytest_asyncio/plugin.py

Lines changed: 92 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@
2626
)
2727

2828
import pytest
29-
from _pytest.mark.structures import get_unpacked_marks
3029
from pytest import (
30+
Class,
3131
Collector,
3232
Config,
3333
FixtureRequest,
3434
Function,
3535
Item,
3636
Metafunc,
37+
Module,
3738
Parser,
3839
PytestCollectionWarning,
3940
PytestDeprecationWarning,
@@ -207,11 +208,13 @@ def _preprocess_async_fixtures(
207208
config = collector.config
208209
asyncio_mode = _get_asyncio_mode(config)
209210
fixturemanager = config.pluginmanager.get_plugin("funcmanage")
210-
event_loop_fixture_id = "event_loop"
211-
for node, mark in collector.iter_markers_with_node("asyncio_event_loop"):
212-
event_loop_fixture_id = node.stash.get(_event_loop_fixture_id, None)
213-
if event_loop_fixture_id:
214-
break
211+
marker = collector.get_closest_marker("asyncio")
212+
scope = marker.kwargs.get("scope", "function") if marker else "function"
213+
if scope == "function":
214+
event_loop_fixture_id = "event_loop"
215+
else:
216+
event_loop_node = _retrieve_scope_root(collector, scope)
217+
event_loop_fixture_id = event_loop_node.stash.get(_event_loop_fixture_id, None)
215218
for fixtures in fixturemanager._arg2fixturedefs.values():
216219
for fixturedef in fixtures:
217220
func = fixturedef.func
@@ -548,48 +551,40 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
548551
def pytest_collectstart(collector: pytest.Collector):
549552
if not isinstance(collector, (pytest.Class, pytest.Module)):
550553
return
551-
# pytest.Collector.own_markers is empty at this point,
552-
# so we rely on _pytest.mark.structures.get_unpacked_marks
553-
marks = get_unpacked_marks(collector.obj, consider_mro=True)
554-
for mark in marks:
555-
if not mark.name == "asyncio_event_loop":
556-
continue
557-
558-
# There seem to be issues when a fixture is shadowed by another fixture
559-
# and both differ in their params.
560-
# https://github.com/pytest-dev/pytest/issues/2043
561-
# https://github.com/pytest-dev/pytest/issues/11350
562-
# As such, we assign a unique name for each event_loop fixture.
563-
# The fixture name is stored in the collector's Stash, so it can
564-
# be injected when setting up the test
565-
event_loop_fixture_id = f"{collector.nodeid}::<event_loop>"
566-
collector.stash[_event_loop_fixture_id] = event_loop_fixture_id
567-
568-
@pytest.fixture(
569-
scope="class" if isinstance(collector, pytest.Class) else "module",
570-
name=event_loop_fixture_id,
571-
)
572-
def scoped_event_loop(
573-
*args, # Function needs to accept "cls" when collected by pytest.Class
574-
event_loop_policy,
575-
) -> Iterator[asyncio.AbstractEventLoop]:
576-
new_loop_policy = event_loop_policy
577-
old_loop_policy = asyncio.get_event_loop_policy()
578-
old_loop = asyncio.get_event_loop()
579-
asyncio.set_event_loop_policy(new_loop_policy)
580-
loop = asyncio.new_event_loop()
581-
asyncio.set_event_loop(loop)
582-
yield loop
583-
loop.close()
584-
asyncio.set_event_loop_policy(old_loop_policy)
585-
asyncio.set_event_loop(old_loop)
586-
587-
# @pytest.fixture does not register the fixture anywhere, so pytest doesn't
588-
# know it exists. We work around this by attaching the fixture function to the
589-
# collected Python class, where it will be picked up by pytest.Class.collect()
590-
# or pytest.Module.collect(), respectively
591-
collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop
592-
break
554+
# There seem to be issues when a fixture is shadowed by another fixture
555+
# and both differ in their params.
556+
# https://github.com/pytest-dev/pytest/issues/2043
557+
# https://github.com/pytest-dev/pytest/issues/11350
558+
# As such, we assign a unique name for each event_loop fixture.
559+
# The fixture name is stored in the collector's Stash, so it can
560+
# be injected when setting up the test
561+
event_loop_fixture_id = f"{collector.nodeid}::<event_loop>"
562+
collector.stash[_event_loop_fixture_id] = event_loop_fixture_id
563+
564+
@pytest.fixture(
565+
scope="class" if isinstance(collector, pytest.Class) else "module",
566+
name=event_loop_fixture_id,
567+
)
568+
def scoped_event_loop(
569+
*args, # Function needs to accept "cls" when collected by pytest.Class
570+
event_loop_policy,
571+
) -> Iterator[asyncio.AbstractEventLoop]:
572+
new_loop_policy = event_loop_policy
573+
old_loop_policy = asyncio.get_event_loop_policy()
574+
old_loop = asyncio.get_event_loop()
575+
asyncio.set_event_loop_policy(new_loop_policy)
576+
loop = asyncio.new_event_loop()
577+
asyncio.set_event_loop(loop)
578+
yield loop
579+
loop.close()
580+
asyncio.set_event_loop_policy(old_loop_policy)
581+
asyncio.set_event_loop(old_loop)
582+
583+
# @pytest.fixture does not register the fixture anywhere, so pytest doesn't
584+
# know it exists. We work around this by attaching the fixture function to the
585+
# collected Python class, where it will be picked up by pytest.Class.collect()
586+
# or pytest.Module.collect(), respectively
587+
collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop
593588

594589

595590
def pytest_collection_modifyitems(
@@ -608,7 +603,9 @@ def pytest_collection_modifyitems(
608603
if _get_asyncio_mode(config) != Mode.AUTO:
609604
return
610605
for item in items:
611-
if isinstance(item, PytestAsyncioFunction):
606+
if isinstance(item, PytestAsyncioFunction) and not item.get_closest_marker(
607+
"asyncio"
608+
):
612609
item.add_marker("asyncio")
613610

614611

@@ -626,32 +623,34 @@ def pytest_collection_modifyitems(
626623

627624
@pytest.hookimpl(tryfirst=True)
628625
def pytest_generate_tests(metafunc: Metafunc) -> None:
629-
for event_loop_provider_node, _ in metafunc.definition.iter_markers_with_node(
630-
"asyncio_event_loop"
631-
):
632-
event_loop_fixture_id = event_loop_provider_node.stash.get(
633-
_event_loop_fixture_id, None
634-
)
635-
if event_loop_fixture_id:
636-
# This specific fixture name may already be in metafunc.argnames, if this
637-
# test indirectly depends on the fixture. For example, this is the case
638-
# when the test depends on an async fixture, both of which share the same
639-
# asyncio_event_loop mark.
640-
if event_loop_fixture_id in metafunc.fixturenames:
641-
continue
642-
fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
643-
if "event_loop" in metafunc.fixturenames:
644-
raise MultipleEventLoopsRequestedError(
645-
_MULTIPLE_LOOPS_REQUESTED_ERROR
646-
% (metafunc.definition.nodeid, event_loop_provider_node.nodeid),
647-
)
648-
# Add the scoped event loop fixture to Metafunc's list of fixture names and
649-
# fixturedefs and leave the actual parametrization to pytest
650-
metafunc.fixturenames.insert(0, event_loop_fixture_id)
651-
metafunc._arg2fixturedefs[
652-
event_loop_fixture_id
653-
] = fixturemanager._arg2fixturedefs[event_loop_fixture_id]
654-
break
626+
marker = metafunc.definition.get_closest_marker("asyncio")
627+
if not marker:
628+
return
629+
scope = marker.kwargs.get("scope", "function")
630+
if scope == "function":
631+
return
632+
event_loop_node = _retrieve_scope_root(metafunc.definition, scope)
633+
event_loop_fixture_id = event_loop_node.stash.get(_event_loop_fixture_id, None)
634+
635+
if event_loop_fixture_id:
636+
# This specific fixture name may already be in metafunc.argnames, if this
637+
# test indirectly depends on the fixture. For example, this is the case
638+
# when the test depends on an async fixture, both of which share the same
639+
# asyncio_event_loop mark.
640+
if event_loop_fixture_id in metafunc.fixturenames:
641+
return
642+
fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
643+
if "event_loop" in metafunc.fixturenames:
644+
raise MultipleEventLoopsRequestedError(
645+
_MULTIPLE_LOOPS_REQUESTED_ERROR
646+
% (metafunc.definition.nodeid, event_loop_node.nodeid),
647+
)
648+
# Add the scoped event loop fixture to Metafunc's list of fixture names and
649+
# fixturedefs and leave the actual parametrization to pytest
650+
metafunc.fixturenames.insert(0, event_loop_fixture_id)
651+
metafunc._arg2fixturedefs[
652+
event_loop_fixture_id
653+
] = fixturemanager._arg2fixturedefs[event_loop_fixture_id]
655654

656655

657656
@pytest.hookimpl(hookwrapper=True)
@@ -826,11 +825,12 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
826825
marker = item.get_closest_marker("asyncio")
827826
if marker is None:
828827
return
829-
event_loop_fixture_id = "event_loop"
830-
for node, mark in item.iter_markers_with_node("asyncio_event_loop"):
831-
event_loop_fixture_id = node.stash.get(_event_loop_fixture_id, None)
832-
if event_loop_fixture_id:
833-
break
828+
scope = marker.kwargs.get("scope", "function")
829+
if scope != "function":
830+
parent_node = _retrieve_scope_root(item, scope)
831+
event_loop_fixture_id = parent_node.stash[_event_loop_fixture_id]
832+
else:
833+
event_loop_fixture_id = "event_loop"
834834
fixturenames = item.fixturenames # type: ignore[attr-defined]
835835
# inject an event loop fixture for all async tests
836836
if "event_loop" in fixturenames:
@@ -846,6 +846,18 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
846846
)
847847

848848

849+
def _retrieve_scope_root(item: Union[Collector, Item], scope: str) -> Collector:
850+
node_type_by_scope = {
851+
"class": Class,
852+
"module": Module,
853+
}
854+
scope_root_type = node_type_by_scope[scope]
855+
for node in reversed(item.listchain()):
856+
if isinstance(node, scope_root_type):
857+
return node
858+
raise RuntimeError()
859+
860+
849861
@pytest.fixture
850862
def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]:
851863
"""Create an instance of the default event loop for each test case."""

‎tests/markers/test_class_marker.py renamed to ‎tests/markers/test_class_scope.py

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def sample_fixture():
2626
return None
2727

2828

29-
def test_asyncio_event_loop_mark_provides_class_scoped_loop_strict_mode(
29+
def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_functions(
3030
pytester: pytest.Pytester,
3131
):
3232
pytester.makepyfile(
@@ -35,15 +35,14 @@ def test_asyncio_event_loop_mark_provides_class_scoped_loop_strict_mode(
3535
import asyncio
3636
import pytest
3737
38-
@pytest.mark.asyncio_event_loop
3938
class TestClassScopedLoop:
4039
loop: asyncio.AbstractEventLoop
4140
42-
@pytest.mark.asyncio
41+
@pytest.mark.asyncio(scope="class")
4342
async def test_remember_loop(self):
4443
TestClassScopedLoop.loop = asyncio.get_running_loop()
4544
46-
@pytest.mark.asyncio
45+
@pytest.mark.asyncio(scope="class")
4746
async def test_this_runs_in_same_loop(self):
4847
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
4948
"""
@@ -53,7 +52,7 @@ async def test_this_runs_in_same_loop(self):
5352
result.assert_outcomes(passed=2)
5453

5554

56-
def test_asyncio_event_loop_mark_provides_class_scoped_loop_auto_mode(
55+
def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_class(
5756
pytester: pytest.Pytester,
5857
):
5958
pytester.makepyfile(
@@ -62,7 +61,7 @@ def test_asyncio_event_loop_mark_provides_class_scoped_loop_auto_mode(
6261
import asyncio
6362
import pytest
6463
65-
@pytest.mark.asyncio_event_loop
64+
@pytest.mark.asyncio(scope="class")
6665
class TestClassScopedLoop:
6766
loop: asyncio.AbstractEventLoop
6867
@@ -74,29 +73,27 @@ async def test_this_runs_in_same_loop(self):
7473
"""
7574
)
7675
)
77-
result = pytester.runpytest("--asyncio-mode=auto")
76+
result = pytester.runpytest("--asyncio-mode=strict")
7877
result.assert_outcomes(passed=2)
7978

8079

81-
def test_asyncio_event_loop_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
80+
def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
8281
pytester.makepyfile(
8382
dedent(
8483
"""\
8584
import asyncio
8685
import pytest
8786
88-
@pytest.mark.asyncio_event_loop
87+
@pytest.mark.asyncio(scope="class")
8988
class TestSuperClassWithMark:
9089
pass
9190
9291
class TestWithoutMark(TestSuperClassWithMark):
9392
loop: asyncio.AbstractEventLoop
9493
95-
@pytest.mark.asyncio
9694
async def test_remember_loop(self):
9795
TestWithoutMark.loop = asyncio.get_running_loop()
9896
99-
@pytest.mark.asyncio
10097
async def test_this_runs_in_same_loop(self):
10198
assert asyncio.get_running_loop() is TestWithoutMark.loop
10299
"""
@@ -106,29 +103,7 @@ async def test_this_runs_in_same_loop(self):
106103
result.assert_outcomes(passed=2)
107104

108105

109-
def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
110-
pytester: pytest.Pytester,
111-
):
112-
pytester.makepyfile(
113-
dedent(
114-
"""\
115-
import asyncio
116-
import pytest
117-
118-
@pytest.mark.asyncio_event_loop
119-
class TestClassScopedLoop:
120-
@pytest.mark.asyncio
121-
async def test_remember_loop(self, event_loop):
122-
pass
123-
"""
124-
)
125-
)
126-
result = pytester.runpytest("--asyncio-mode=strict")
127-
result.assert_outcomes(errors=1)
128-
result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")
129-
130-
131-
def test_asyncio_event_loop_mark_allows_specifying_the_loop_policy(
106+
def test_asyncio_mark_respects_the_loop_policy(
132107
pytester: pytest.Pytester,
133108
):
134109
pytester.makepyfile(
@@ -144,10 +119,8 @@ class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
144119
def event_loop_policy():
145120
return CustomEventLoopPolicy()
146121
147-
@pytest.mark.asyncio_event_loop
148-
class TestUsesCustomEventLoop:
149-
150-
@pytest.mark.asyncio
122+
@pytest.mark.asyncio(scope="class")
123+
class TestUsesCustomEventLoopPolicy:
151124
async def test_uses_custom_event_loop_policy(self):
152125
assert isinstance(
153126
asyncio.get_event_loop_policy(),
@@ -167,7 +140,7 @@ async def test_does_not_use_custom_event_loop_policy():
167140
result.assert_outcomes(passed=2)
168141

169142

170-
def test_asyncio_event_loop_mark_allows_specifying_multiple_loop_policies(
143+
def test_asyncio_mark_respects_parametrized_loop_policies(
171144
pytester: pytest.Pytester,
172145
):
173146
pytester.makepyfile(
@@ -178,6 +151,7 @@ def test_asyncio_event_loop_mark_allows_specifying_multiple_loop_policies(
178151
import pytest
179152
180153
@pytest.fixture(
154+
scope="class",
181155
params=[
182156
asyncio.DefaultEventLoopPolicy(),
183157
asyncio.DefaultEventLoopPolicy(),
@@ -186,8 +160,8 @@ def test_asyncio_event_loop_mark_allows_specifying_multiple_loop_policies(
186160
def event_loop_policy(request):
187161
return request.param
188162
163+
@pytest.mark.asyncio(scope="class")
189164
class TestWithDifferentLoopPolicies:
190-
@pytest.mark.asyncio
191165
async def test_parametrized_loop(self, request):
192166
pass
193167
"""
@@ -197,7 +171,7 @@ async def test_parametrized_loop(self, request):
197171
result.assert_outcomes(passed=2)
198172

199173

200-
def test_asyncio_event_loop_mark_provides_class_scoped_loop_to_fixtures(
174+
def test_asyncio_mark_provides_class_scoped_loop_to_fixtures(
201175
pytester: pytest.Pytester,
202176
):
203177
pytester.makepyfile(
@@ -208,7 +182,7 @@ def test_asyncio_event_loop_mark_provides_class_scoped_loop_to_fixtures(
208182
import pytest
209183
import pytest_asyncio
210184
211-
@pytest.mark.asyncio_event_loop
185+
@pytest.mark.asyncio(scope="class")
212186
class TestClassScopedLoop:
213187
loop: asyncio.AbstractEventLoop
214188

‎tests/markers/test_module_marker.py renamed to ‎tests/markers/test_module_scope.py

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,19 @@ def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester
5959
import asyncio
6060
import pytest
6161
62-
pytestmark = pytest.mark.asyncio_event_loop
62+
pytestmark = pytest.mark.asyncio(scope="module")
6363
6464
loop: asyncio.AbstractEventLoop
6565
66-
@pytest.mark.asyncio
6766
async def test_remember_loop():
6867
global loop
6968
loop = asyncio.get_running_loop()
7069
71-
@pytest.mark.asyncio
7270
async def test_this_runs_in_same_loop():
7371
global loop
7472
assert asyncio.get_running_loop() is loop
7573
7674
class TestClassA:
77-
@pytest.mark.asyncio
7875
async def test_this_runs_in_same_loop(self):
7976
global loop
8077
assert asyncio.get_running_loop() is loop
@@ -85,36 +82,6 @@ async def test_this_runs_in_same_loop(self):
8582
result.assert_outcomes(passed=3)
8683

8784

88-
def test_asyncio_mark_provides_class_scoped_loop_auto_mode(pytester: Pytester):
89-
pytester.makepyfile(
90-
dedent(
91-
"""\
92-
import asyncio
93-
import pytest
94-
95-
pytestmark = pytest.mark.asyncio_event_loop
96-
97-
loop: asyncio.AbstractEventLoop
98-
99-
async def test_remember_loop():
100-
global loop
101-
loop = asyncio.get_running_loop()
102-
103-
async def test_this_runs_in_same_loop():
104-
global loop
105-
assert asyncio.get_running_loop() is loop
106-
107-
class TestClassA:
108-
async def test_this_runs_in_same_loop(self):
109-
global loop
110-
assert asyncio.get_running_loop() is loop
111-
"""
112-
)
113-
)
114-
result = pytester.runpytest("--asyncio-mode=auto")
115-
result.assert_outcomes(passed=3)
116-
117-
11885
def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
11986
pytester: Pytester,
12087
):
@@ -124,9 +91,8 @@ def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
12491
import asyncio
12592
import pytest
12693
127-
pytestmark = pytest.mark.asyncio_event_loop
94+
pytestmark = pytest.mark.asyncio(scope="module")
12895
129-
@pytest.mark.asyncio
13096
async def test_remember_loop(event_loop):
13197
pass
13298
"""
@@ -137,7 +103,7 @@ async def test_remember_loop(event_loop):
137103
result.stdout.fnmatch_lines("*MultipleEventLoopsRequestedError: *")
138104

139105

140-
def test_asyncio_event_loop_mark_allows_specifying_the_loop_policy(
106+
def test_asyncio_mark_respects_the_loop_policy(
141107
pytester: Pytester,
142108
):
143109
pytester.makepyfile(
@@ -157,13 +123,12 @@ class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
157123
158124
from .custom_policy import CustomEventLoopPolicy
159125
160-
pytestmark = pytest.mark.asyncio_event_loop
126+
pytestmark = pytest.mark.asyncio(scope="module")
161127
162128
@pytest.fixture(scope="module")
163129
def event_loop_policy():
164130
return CustomEventLoopPolicy()
165131
166-
@pytest.mark.asyncio
167132
async def test_uses_custom_event_loop_policy():
168133
assert isinstance(
169134
asyncio.get_event_loop_policy(),
@@ -178,7 +143,8 @@ async def test_uses_custom_event_loop_policy():
178143
179144
from .custom_policy import CustomEventLoopPolicy
180145
181-
@pytest.mark.asyncio
146+
pytestmark = pytest.mark.asyncio(scope="module")
147+
182148
async def test_does_not_use_custom_event_loop_policy():
183149
assert not isinstance(
184150
asyncio.get_event_loop_policy(),
@@ -191,7 +157,7 @@ async def test_does_not_use_custom_event_loop_policy():
191157
result.assert_outcomes(passed=2)
192158

193159

194-
def test_asyncio_event_loop_mark_allows_specifying_multiple_loop_policies(
160+
def test_asyncio_mark_respects_parametrized_loop_policies(
195161
pytester: Pytester,
196162
):
197163
pytester.makepyfile(
@@ -201,7 +167,7 @@ def test_asyncio_event_loop_mark_allows_specifying_multiple_loop_policies(
201167
202168
import pytest
203169
204-
pytestmark = pytest.mark.asyncio_event_loop
170+
pytestmark = pytest.mark.asyncio(scope="module")
205171
206172
@pytest.fixture(
207173
scope="module",
@@ -213,17 +179,16 @@ def test_asyncio_event_loop_mark_allows_specifying_multiple_loop_policies(
213179
def event_loop_policy(request):
214180
return request.param
215181
216-
@pytest.mark.asyncio
217182
async def test_parametrized_loop():
218183
pass
219184
"""
220185
)
221186
)
222-
result = pytester.runpytest_subprocess("--asyncio-mode=strict", "--setup-show")
187+
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
223188
result.assert_outcomes(passed=2)
224189

225190

226-
def test_asyncio_event_loop_mark_provides_module_scoped_loop_to_fixtures(
191+
def test_asyncio_mark_provides_module_scoped_loop_to_fixtures(
227192
pytester: Pytester,
228193
):
229194
pytester.makepyfile(
@@ -234,16 +199,15 @@ def test_asyncio_event_loop_mark_provides_module_scoped_loop_to_fixtures(
234199
import pytest
235200
import pytest_asyncio
236201
237-
pytestmark = pytest.mark.asyncio_event_loop
202+
pytestmark = pytest.mark.asyncio(scope="module")
238203
239204
loop: asyncio.AbstractEventLoop
240205
241-
@pytest_asyncio.fixture
206+
@pytest_asyncio.fixture(scope="module")
242207
async def my_fixture():
243208
global loop
244209
loop = asyncio.get_running_loop()
245210
246-
@pytest.mark.asyncio
247211
async def test_runs_is_same_loop_as_fixture(my_fixture):
248212
global loop
249213
assert asyncio.get_running_loop() is loop

‎tox.ini

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
minversion = 3.14.0
3-
envlist = py38, py39, py310, py311, py312, pytest-min
3+
envlist = py38, py39, py310, py311, py312, pytest-min, docs
44
isolated_build = true
55
passenv =
66
CI
@@ -23,6 +23,16 @@ commands = make test
2323
allowlist_externals =
2424
make
2525

26+
[testenv:docs]
27+
extras = docs
28+
deps =
29+
--requirement dependencies/docs/requirements.txt
30+
--constraint dependencies/docs/constraints.txt
31+
change_dir = docs
32+
commands = make html
33+
allowlist_externals =
34+
make
35+
2636
[gh-actions]
2737
python =
2838
3.8: py38, pytest-min

0 commit comments

Comments
 (0)
Please sign in to comment.