diff --git a/changelog/7259.deprecation.rst b/changelog/7259.deprecation.rst index e450b3599..c0307740d 100644 --- a/changelog/7259.deprecation.rst +++ b/changelog/7259.deprecation.rst @@ -1 +1,3 @@ ``py.path.local`` arguments for hooks have been deprecated. See :ref:`the deprecation note ` for full details. + +``py.path.local`` arguments to Node constructors have been deprecated. See :ref:`the deprecation note ` for full details. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index cfe7f489f..b82dd8521 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -18,6 +18,25 @@ Deprecated Features Below is a complete list of all pytest features which are considered deprecated. Using those features will issue :class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +.. _node-ctor-fspath-deprecation: + +``fspath`` argument for Node constructors replaced with ``pathlib.Path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 + +In order to support the transition from ``py.path.local`` to :mod:`pathlib`, +the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like +:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()` +is now deprecated. + +Plugins which construct nodes should pass the ``path`` argument, of type +:class:`pathlib.Path`, instead of the ``fspath`` argument. + +Plugins which implement custom items and collectors are encouraged to replace +``py.path.local`` ``fspath`` parameters with ``pathlib.Path`` parameters, and +drop any other usage of the ``py`` library if possible. + .. _legacy-path-hooks-deprecated: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 0c5db6d53..452128d07 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -101,6 +101,14 @@ HOOK_LEGACY_PATH_ARG = UnformattedWarning( "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path", ) +NODE_CTOR_FSPATH_ARG = UnformattedWarning( + PytestDeprecationWarning, + "The (fspath: py.path.local) argument to {node_type_name} is deprecated. " + "Please use the (path: pathlib.Path) argument instead.\n" + "See https://docs.pytest.org/en/latest/deprecations.html" + "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", +) + WARNS_NONE_ARG = PytestDeprecationWarning( "Passing None to catch any warning has been deprecated, pass no arguments instead:\n" " Replace pytest.warns(None) by simply pytest.warns()." diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 33bf1062b..05cf01fc6 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -28,6 +28,7 @@ from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH +from _pytest.deprecated import NODE_CTOR_FSPATH_ARG from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords @@ -101,7 +102,18 @@ def _check_path(path: Path, fspath: LEGACY_PATH) -> None: ) -def _imply_path(path: Optional[Path], fspath: Optional[LEGACY_PATH]) -> Path: +def _imply_path( + node_type: Type["Node"], + path: Optional[Path], + fspath: Optional[LEGACY_PATH], +) -> Path: + if fspath is not None: + warnings.warn( + NODE_CTOR_FSPATH_ARG.format( + node_type_name=node_type.__name__, + ), + stacklevel=3, + ) if path is not None: if fspath is not None: _check_path(path, fspath) @@ -196,7 +208,7 @@ class Node(metaclass=NodeMeta): #: Filesystem path where this node was collected from (can be None). if path is None and fspath is None: path = getattr(parent, "path", None) - self.path = _imply_path(path, fspath=fspath) + self.path = _imply_path(type(self), path, fspath=fspath) # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. @@ -573,7 +585,7 @@ class FSCollector(Collector): assert path is None path = path_or_parent - path = _imply_path(path, fspath=fspath) + path = _imply_path(type(self), path, fspath=fspath) if name is None: name = path.name if parent is not None and parent.path != path: diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index bf796a339..0f5e483ce 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -215,3 +215,16 @@ def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None: "*Please use pytest_load_initial_conftests hook instead.*", ] ) + + +def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: + mod = pytester.getmodulecol("") + + with pytest.warns( + pytest.PytestDeprecationWarning, + match=re.escape("The (fspath: py.path.local) argument to File is deprecated."), + ): + pytest.File.from_parent( + parent=mod.parent, + fspath=legacy_path("bla"), + ) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 8ef321fd3..89e8b0890 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -4,7 +4,7 @@ pytest-asyncio==0.16.0 pytest-bdd==4.1.0 pytest-cov==3.0.0 pytest-django==4.4.0 -pytest-flakes==4.0.3 +pytest-flakes==4.0.4 pytest-html==3.1.1 pytest-mock==3.6.1 pytest-rerunfailures==10.2