From 7706fd68401c7730ad06103577c10cc4cf031221 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 23 Oct 2021 22:05:56 +0300 Subject: [PATCH] nodes: deprecate fspath arguments to node constructors This is unfortunately a dependency on `py.path` which cannot be moved to an external plugins or eased in any way, so has to be deprecated in order for pytest to be able to eventually remove the dependency on `py`. --- changelog/7259.deprecation.rst | 2 ++ doc/en/deprecations.rst | 19 +++++++++++++++++++ src/_pytest/deprecated.py | 8 ++++++++ src/_pytest/nodes.py | 18 +++++++++++++++--- testing/deprecated_test.py | 13 +++++++++++++ testing/plugins_integration/requirements.txt | 2 +- 6 files changed, 58 insertions(+), 4 deletions(-) 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