Skip to content
Open
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/1475.removed.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed the deprecated ``scope`` keyword argument of the ``asyncio`` marker. Passing ``scope=`` (or any other unknown keyword argument) to ``@pytest.mark.asyncio`` now raises ``pytest.UsageError`` instead of emitting a deprecation warning. Use ``loop_scope=`` instead.
2 changes: 1 addition & 1 deletion docs/how-to-guides/migrate_from_0_23.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ The following steps assume that your test suite has no re-implementations of the

1. Explicitly set the *loop_scope* of async fixtures by replacing occurrences of ``@pytest.fixture(scope="…")`` and ``@pytest_asyncio.fixture(scope="…")`` with ``@pytest_asyncio.fixture(loop_scope="…", scope="…")`` such that *loop_scope* and *scope* are the same. If you use auto mode, resolve all import errors from missing imports of *pytest_asyncio*. If your async fixtures all use the same *loop_scope*, you may choose to set the *asyncio_default_fixture_loop_scope* configuration option to that loop scope, instead.
2. If you haven't set *asyncio_default_fixture_loop_scope*, set it to *function* to address the deprecation warning about the unset configuration option.
3. Change all occurrences of ``pytest.mark.asyncio(scope="…")`` to ``pytest.mark.asyncio(loop_scope="…")`` to address the deprecation warning about the *scope* argument to the *asyncio* marker.
3. Change all occurrences of ``pytest.mark.asyncio(scope="…")`` to ``pytest.mark.asyncio(loop_scope="…")``. The *scope* argument to the *asyncio* marker was deprecated in v0.23 and removed in v2.0; passing it now raises a usage error.
42 changes: 16 additions & 26 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ def _loop_scope(self) -> _ScopeName:
marker = self.get_closest_marker("asyncio")
assert marker is not None
default_loop_scope = _get_default_test_loop_scope(self.config)
loop_scope = marker.kwargs.get("loop_scope") or marker.kwargs.get("scope")
loop_scope = marker.kwargs.get("loop_scope")
if loop_scope is None:
return default_loop_scope
else:
Expand Down Expand Up @@ -946,16 +946,6 @@ def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None:
return hook_result


_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR = """\
An asyncio pytest marker defines both "scope" and "loop_scope", \
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.
"""

_INVALID_LOOP_FACTORIES_KWARG = """\
mark.asyncio 'loop_factories' must be a non-empty sequence of strings.
"""
Expand All @@ -972,13 +962,7 @@ def _parse_asyncio_marker(
) -> tuple[_ScopeName | None, Sequence[str] | None]:
assert asyncio_marker.name == "asyncio"
_validate_asyncio_marker(asyncio_marker)
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"
)
scope = asyncio_marker.kwargs.get("loop_scope")
if scope is not None:
assert scope in {"function", "class", "module", "package", "session"}
marker_value = asyncio_marker.kwargs.get("loop_factories")
Expand All @@ -994,16 +978,22 @@ def _parse_asyncio_marker(
return scope, marker_value


_ALLOWED_ASYNCIO_MARKER_KWARGS = {"loop_scope", "loop_factories"}


def _validate_asyncio_marker(asyncio_marker: Mark) -> None:
if asyncio_marker.args or (
asyncio_marker.kwargs
and set(asyncio_marker.kwargs) - {"loop_scope", "scope", "loop_factories"}
):
msg = (
"mark.asyncio accepts only keyword arguments 'loop_scope' and"
" 'loop_factories'."
if asyncio_marker.args:
raise pytest.UsageError(
"mark.asyncio does not accept positional arguments. "
"Use the 'loop_scope' or 'loop_factories' keyword arguments instead."
)
unknown_kwargs = sorted(set(asyncio_marker.kwargs) - _ALLOWED_ASYNCIO_MARKER_KWARGS)
if unknown_kwargs:
formatted = ", ".join(repr(name) for name in unknown_kwargs)
raise pytest.UsageError(
f"mark.asyncio received unexpected keyword argument(s): {formatted}. "
"Only 'loop_scope' and 'loop_factories' are accepted."
)
raise ValueError(msg)


def _get_default_test_loop_scope(config: Config) -> Any:
Expand Down
15 changes: 1 addition & 14 deletions tests/markers/test_function_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,7 @@ async def test_raises():
"""))
result = pytester.runpytest("--asyncio-mode=strict")
result.assert_outcomes(errors=1)


def test_warns_when_scope_argument_is_present(pytester: Pytester):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest

@pytest.mark.asyncio(scope="function")
async def test_warns():
...
"""))
result = pytester.runpytest("--asyncio-mode=strict", "-W", "default")
result.assert_outcomes(passed=1, warnings=1)
result.stdout.fnmatch_lines("*DeprecationWarning*")
result.stdout.fnmatch_lines("*unexpected keyword argument*'scope'*")


def test_asyncio_mark_respects_the_loop_policy(
Expand Down
26 changes: 21 additions & 5 deletions tests/markers/test_invalid_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ async def test_anything():
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio accepts only keyword arguments*"]
)
result.stdout.fnmatch_lines(["*mark.asyncio does not accept positional arguments*"])


def test_error_when_wrong_keyword_argument_is_passed(
Expand All @@ -53,7 +51,7 @@ async def test_anything():
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio accepts only keyword arguments*"]
["*mark.asyncio received unexpected keyword argument(s): 'cope'*"]
)


Expand All @@ -71,7 +69,25 @@ async def test_anything():
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*ValueError: mark.asyncio accepts only keyword arguments*"]
["*mark.asyncio received unexpected keyword argument(s): 'more'*"]
)


def test_error_when_scope_keyword_argument_is_passed(
pytester: pytest.Pytester,
):
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
pytester.makepyfile(dedent("""\
import pytest

@pytest.mark.asyncio(scope="session")
async def test_anything():
pass
"""))
result = pytester.runpytest("--assert=plain")
result.assert_outcomes(errors=1)
result.stdout.fnmatch_lines(
["*mark.asyncio received unexpected keyword argument(s): 'scope'*"]
)


Expand Down
Loading