Skip to content

Commit

Permalink
[feat] Deprecates the optional scope keyword argument of asyncio ma…
Browse files Browse the repository at this point in the history
…rkers. Users are encouraged to use the `loop_scope` keyword argument. The `loop_scope` kwarg does exactly the same, though its naming is consistent with the `loop_scope` kwarg of ``pytest_asyncio.fixture``.

Signed-off-by: Michael Seifert <[email protected]>
  • Loading branch information
seifertm committed Jul 13, 2024
1 parent 1bb4299 commit 7f802bb
Show file tree
Hide file tree
Showing 22 changed files with 96 additions and 68 deletions.
4 changes: 2 additions & 2 deletions docs/source/concepts_module_scope_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
loop: asyncio.AbstractEventLoop


@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
async def test_remember_loop():
global loop
loop = asyncio.get_running_loop()


@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
async def test_runs_in_a_loop():
global loop
assert asyncio.get_running_loop() is loop
2 changes: 1 addition & 1 deletion docs/source/how-to-guides/class_scoped_loop_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest


@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestInOneEventLoopPerClass:
loop: asyncio.AbstractEventLoop

Expand Down
2 changes: 1 addition & 1 deletion docs/source/how-to-guides/module_scoped_loop_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")

loop: asyncio.AbstractEventLoop

Expand Down
2 changes: 1 addition & 1 deletion docs/source/how-to-guides/package_scoped_loop_example.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import pytest

pytestmark = pytest.mark.asyncio(scope="package")
pytestmark = pytest.mark.asyncio(loop_scope="package")
2 changes: 1 addition & 1 deletion docs/source/how-to-guides/run_class_tests_in_same_loop.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
======================================================
How to run all tests in a class in the same event loop
======================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(scope="class")``.
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="class")``.
This is easily achieved by using the *asyncio* marker as a class decorator.

.. include:: class_scoped_loop_example.py
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=======================================================
How to run all tests in a module in the same event loop
=======================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(scope="module")``.
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="module")``.
This is easily achieved by adding a `pytestmark` statement to your module.

.. include:: module_scoped_loop_example.py
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
========================================================
How to run all tests in a package in the same event loop
========================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(scope="package")``.
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="package")``.
Add the following code to the ``__init__.py`` of the test package:

.. include:: package_scoped_loop_example.py
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
==========================================================
How to run all tests in the session in the same event loop
==========================================================
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(scope="session")``.
All tests can be run inside the same event loop by marking them with ``pytest.mark.asyncio(loop_scope="session")``.
The easiest way to mark all tests is via a ``pytest_collection_modifyitems`` hook in the ``conftest.py`` at the root folder of your test suite.

.. include:: session_scoped_loop_example.py
Expand Down
2 changes: 1 addition & 1 deletion docs/source/how-to-guides/session_scoped_loop_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

def pytest_collection_modifyitems(items):
pytest_asyncio_tests = (item for item in items if is_async_test(item))
session_scope_marker = pytest.mark.asyncio(scope="session")
session_scope_marker = pytest.mark.asyncio(loop_scope="session")
for async_test in pytest_asyncio_tests:
async_test.add_marker(session_scope_marker, append=False)
4 changes: 4 additions & 0 deletions docs/source/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Changelog
=========

0.24.0 (UNRELEASED)
===================
- Deprecates the optional `scope` keyword argument of asyncio markers. Users are encouraged to use the `loop_scope` keyword argument. The `loop_scope` kwarg does exactly the same, though its naming is consistent with the `loop_scope` kwarg of ``pytest_asyncio.fixture``.

0.23.8 (UNRELEASED)
===================
- Fixes a bug that caused duplicate markers in async tests `#813 <https://github.com/pytest-dev/pytest-asyncio/issues/813>`_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ def event_loop_policy(request):
return CustomEventLoopPolicy()


@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
async def test_uses_custom_event_loop_policy():
assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest


@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest_asyncio


@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")

loop: asyncio.AbstractEventLoop

Expand Down
11 changes: 9 additions & 2 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,11 +995,18 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
but it should only use "loop_scope".
"""

_MARKER_SCOPE_KWARG_DEPRECATION_WARNING = """\
The "scope" keyword argument to the asyncio marker has been deprecated. \
Please use the "loop_scope" argument instead.
"""


def _get_marked_loop_scope(asyncio_marker: Mark) -> _ScopeName:
assert asyncio_marker.name == "asyncio"
if "scope" in asyncio_marker.kwargs and "loop_scope" in asyncio_marker.kwargs:
raise pytest.UsageError(_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR)
if "scope" in asyncio_marker.kwargs:
if "loop_scope" in asyncio_marker.kwargs:
raise pytest.UsageError(_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR)
warnings.warn(PytestDeprecationWarning(_MARKER_SCOPE_KWARG_DEPRECATION_WARNING))
scope = asyncio_marker.kwargs.get("loop_scope") or asyncio_marker.kwargs.get(
"scope", "function"
)
Expand Down
4 changes: 2 additions & 2 deletions tests/async_fixtures/test_async_fixtures_with_finalizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import pytest


@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
async def test_module_with_event_loop_finalizer(port_with_event_loop_finalizer):
await asyncio.sleep(0.01)
assert port_with_event_loop_finalizer


@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
async def test_module_with_get_event_loop_finalizer(port_with_get_event_loop_finalizer):
await asyncio.sleep(0.01)
assert port_with_get_event_loop_finalizer
Expand Down
20 changes: 10 additions & 10 deletions tests/markers/test_class_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_functions(
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
async def test_remember_loop(self):
TestClassScopedLoop.loop = asyncio.get_running_loop()
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
async def test_this_runs_in_same_loop(self):
assert asyncio.get_running_loop() is TestClassScopedLoop.loop
"""
Expand All @@ -62,7 +62,7 @@ def test_asyncio_mark_provides_class_scoped_loop_when_applied_to_class(
import asyncio
import pytest
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
Expand All @@ -87,7 +87,7 @@ def test_asyncio_mark_raises_when_class_scoped_is_request_without_class(
import asyncio
import pytest
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
async def test_has_no_surrounding_class():
pass
"""
Expand All @@ -107,7 +107,7 @@ def test_asyncio_mark_is_inherited_to_subclasses(pytester: pytest.Pytester):
import asyncio
import pytest
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestSuperClassWithMark:
pass
Expand Down Expand Up @@ -183,7 +183,7 @@ def test_asyncio_mark_respects_parametrized_loop_policies(
def event_loop_policy(request):
return request.param
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestWithDifferentLoopPolicies:
async def test_parametrized_loop(self, request):
pass
Expand All @@ -205,7 +205,7 @@ def test_asyncio_mark_provides_class_scoped_loop_to_fixtures(
import pytest
import pytest_asyncio
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestClassScopedLoop:
loop: asyncio.AbstractEventLoop
Expand Down Expand Up @@ -242,7 +242,7 @@ async def async_fixture(self):
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(scope="function")
@pytest.mark.asyncio(loop_scope="function")
async def test_runs_in_different_loop_as_fixture(self, async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
Expand Down Expand Up @@ -277,7 +277,7 @@ def sets_event_loop_to_none(self):
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(self, sets_event_loop_to_none, n):
Expand All @@ -297,7 +297,7 @@ def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_be
"""\
import pytest
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestClass:
async def test_anything(self):
pass
Expand Down
17 changes: 17 additions & 0 deletions tests/markers/test_function_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ async def test_raises():
result.assert_outcomes(errors=1)


def test_warns_when_scope_argument_is_present(pytester: Pytester):
pytester.makepyfile(
dedent(
"""\
import pytest
@pytest.mark.asyncio(scope="function")
async def test_warns():
...
"""
)
)
result = pytester.runpytest_subprocess("--asyncio-mode=strict")
result.assert_outcomes(passed=1, warnings=2)
result.stdout.fnmatch_lines("*DeprecationWarning*")


def test_function_scope_supports_explicit_event_loop_fixture_request(
pytester: Pytester,
):
Expand Down
22 changes: 11 additions & 11 deletions tests/markers/test_module_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_asyncio_mark_provides_module_scoped_loop_strict_mode(pytester: Pytester
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")
loop: asyncio.AbstractEventLoop
Expand Down Expand Up @@ -94,7 +94,7 @@ def test_raise_when_event_loop_fixture_is_requested_in_addition_to_scoped_loop(
import asyncio
import pytest
pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")
async def test_remember_loop(event_loop):
pass
Expand Down Expand Up @@ -126,7 +126,7 @@ class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")
@pytest.fixture(scope="module")
def event_loop_policy():
Expand All @@ -146,7 +146,7 @@ async def test_uses_custom_event_loop_policy():
from .custom_policy import CustomEventLoopPolicy
pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")
async def test_does_not_use_custom_event_loop_policy():
assert not isinstance(
Expand All @@ -170,7 +170,7 @@ def test_asyncio_mark_respects_parametrized_loop_policies(
import pytest
pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")
@pytest.fixture(
scope="module",
Expand Down Expand Up @@ -202,7 +202,7 @@ def test_asyncio_mark_provides_module_scoped_loop_to_fixtures(
import pytest
import pytest_asyncio
pytestmark = pytest.mark.asyncio(scope="module")
pytestmark = pytest.mark.asyncio(loop_scope="module")
loop: asyncio.AbstractEventLoop
Expand Down Expand Up @@ -239,7 +239,7 @@ async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(scope="class")
@pytest.mark.asyncio(loop_scope="class")
class TestMixedScopes:
async def test_runs_in_different_loop_as_fixture(self, async_fixture):
global loop
Expand Down Expand Up @@ -271,7 +271,7 @@ async def async_fixture():
global loop
loop = asyncio.get_running_loop()
@pytest.mark.asyncio(scope="function")
@pytest.mark.asyncio(loop_scope="function")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
Expand Down Expand Up @@ -301,7 +301,7 @@ async def async_fixture():
loop = asyncio.get_running_loop()
yield
@pytest.mark.asyncio(scope="function")
@pytest.mark.asyncio(loop_scope="function")
async def test_runs_in_different_loop_as_fixture(async_fixture):
global loop
assert asyncio.get_running_loop() is not loop
Expand Down Expand Up @@ -334,7 +334,7 @@ def sets_event_loop_to_none():
return asyncio.run(asyncio.sleep(0))
# asyncio.run() sets the current event loop to None when finished
@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
# parametrization may impact fixture ordering
@pytest.mark.parametrize("n", (0, 1))
async def test_does_not_fail(sets_event_loop_to_none, n):
Expand All @@ -354,7 +354,7 @@ def test_standalone_test_does_not_trigger_warning_about_no_current_event_loop_be
"""\
import pytest
@pytest.mark.asyncio(scope="module")
@pytest.mark.asyncio(loop_scope="module")
async def test_anything():
pass
"""
Expand Down
Loading

0 comments on commit 7f802bb

Please sign in to comment.