From 56e95333546f349c3fa85818ed99a9d64e6ca529 Mon Sep 17 00:00:00 2001 From: Sadra Barikbin Date: Sun, 14 Jan 2024 23:53:10 +0330 Subject: [PATCH] Apply all but one review comments --- doc/en/deprecations.rst | 13 +++++------- src/_pytest/assertion/rewrite.py | 7 ++++--- src/_pytest/doctest.py | 5 ++++- src/_pytest/python.py | 4 +++- testing/deprecated_test.py | 28 ++++++++++++------------- testing/test_doctest.py | 35 ++++++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 28 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index c7c04f859..c1c644d8c 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -280,18 +280,15 @@ The accompanying ``py.path.local`` based paths have been deprecated: plugins whi .. _item-funcargs-deprecation: -Accessing ``item.funcargs`` with not directly requested fixture names +Accessing ``item.funcargs`` with non-directly requested fixture names ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 8.0 -.. versionremoved:: 9.0 +.. versionremoved:: 8.1 -Accessing ``item.funcargs`` with not directly requested fixture names issues warning and -will be erroneous starting from pytest 9. Directly requested fixtures are the direct args -to the test, the ``usefixtures`` fixtures and the ``autouse`` ones. +Accessing ``item.funcargs`` with non-directly requested fixture names issues a warning and will be erroneous starting from pytest 9. +Directly requested fixtures are the direct arguments to the test, ``usefixtures`` fixtures and ``autouse`` fixtures. -To request fixtures other than the directly requested ones, user could use -``request.getfixturevalue`` instead. +To request a fixture other than the directly requested ones, use :func:`request.getfixturevalue ` instead. .. _nose-deprecation: diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 149101e71..b1a644cbf 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -17,6 +17,7 @@ from collections import defaultdict from pathlib import Path from pathlib import PurePath from typing import Callable +from typing import DefaultDict from typing import Dict from typing import IO from typing import Iterable @@ -668,9 +669,9 @@ class AssertionRewriter(ast.NodeVisitor): else: self.enable_assertion_pass_hook = False self.source = source - self.scope: tuple[ast.AST, ...] = () - self.variables_overwrite: defaultdict[ - tuple[ast.AST, ...], Dict[str, str] + self.scope: Tuple[ast.AST, ...] = () + self.variables_overwrite: DefaultDict[ + Tuple[ast.AST, ...], Dict[str, str] ] = defaultdict(dict) def run(self, mod: ast.Module) -> None: diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 4ce32a298..a55533436 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -40,6 +40,7 @@ from _pytest.outcomes import OutcomeException from _pytest.outcomes import skip from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path +from _pytest.python import DeprecatingFuncArgs from _pytest.python import Module from _pytest.python_api import approx from _pytest.warning_types import PytestWarning @@ -284,7 +285,9 @@ class DoctestItem(Item): return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: Dict[str, object] = DeprecatingFuncArgs( + self._fixtureinfo.initialnames + ) self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] def setup(self) -> None: diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ca679ab73..5d9aa9655 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1664,10 +1664,12 @@ def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: class DeprecatingFuncArgs(Dict[str, object]): def __init__(self, initialnames: Sequence[str]) -> None: super().__init__() + self.warned: bool = False self.initialnames: Final = initialnames def __getitem__(self, key: str) -> object: - if key not in self.initialnames: + if not self.warned and key not in self.initialnames: + self.warned = True warnings.warn(ITEM_FUNCARGS_MEMBERS, stacklevel=2) return super().__getitem__(key) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 56fee570a..8e201fce5 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -195,7 +195,7 @@ def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None: def test_deprecated_access_to_item_funcargs(pytester: Pytester) -> None: - module = pytester.makepyfile( + pytester.makepyfile( """ import pytest @@ -207,19 +207,17 @@ def test_deprecated_access_to_item_funcargs(pytester: Pytester) -> None: def fixture2(fixture1): return None - def test(fixture2): - pass + def test(request, fixture2): + with pytest.warns( + pytest.PytestRemovedIn9Warning, + match=r"Accessing `item.funcargs` with a fixture", + ) as record: + request.node.funcargs["fixture1"] + assert request.node.funcargs.warned + request.node.funcargs.warned = False + request.node.funcargs["fixture2"] + assert len(record) == 1 """ ) - test = pytester.genitems((pytester.getmodulecol(module),))[0] - assert isinstance(test, pytest.Function) - test.session._setupstate.setup(test) - test.setup() - with pytest.warns( - pytest.PytestRemovedIn9Warning, - match=r"Accessing `item.funcargs` with a fixture", - ) as record: - test.funcargs["fixture1"] - assert len(record) == 1 - test.funcargs["fixture2"] - assert len(record) == 1 + output = pytester.runpytest() + output.assert_outcomes(passed=1) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index f4d3155c4..28b3d65c7 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -881,6 +881,41 @@ class TestDoctests: result = pytester.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines(["*collected 1 item*"]) + def test_deprecated_access_to_item_funcargs(self, pytester: Pytester): + pytester.makeconftest( + """ + import pytest + + @pytest.fixture + def fixture1(): + return None + + @pytest.fixture(autouse=True) + def fixture2(fixture1): + return None + """ + ) + pytester.makepyfile( + """ + ''' + >>> import pytest + >>> request = getfixture('request') + >>> with pytest.warns( + ... pytest.PytestRemovedIn9Warning, + ... match=r"Accessing `item.funcargs` with a fixture", + ... ) as record: + ... request.node.funcargs["fixture1"] + ... assert request.node.funcargs.warned + ... request.node.funcargs.warned = False + ... request.node.funcargs["fixture2"] + >>> len(record) + 1 + ''' + """ + ) + result = pytester.runpytest("--doctest-modules") + result.assert_outcomes(passed=1) + class TestLiterals: @pytest.mark.parametrize("config_mode", ["ini", "comment"])