Skip to content

Commit d4907e8

Browse files
committed
nodes: remove deprecated fspath Node constructor parameter
Deprecated feature scheduled for removal in pytest 9. Part of #13893.
1 parent d889ca0 commit d4907e8

7 files changed

Lines changed: 58 additions & 130 deletions

File tree

doc/en/deprecations.rst

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -183,46 +183,6 @@ In ``9.0``, the warning will turn into an error, and in ``9.1`` :func:`pytest.im
183183
:class:`ImportError` by passing it to ``exc_type``.
184184

185185

186-
.. _node-ctor-fspath-deprecation:
187-
188-
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
189-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
190-
191-
.. deprecated:: 7.0
192-
193-
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
194-
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
195-
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
196-
is now deprecated.
197-
198-
Plugins which construct nodes should pass the ``path`` argument, of type
199-
:class:`pathlib.Path`, instead of the ``fspath`` argument.
200-
201-
Plugins which implement custom items and collectors are encouraged to replace
202-
``fspath`` parameters (``py.path.local``) with ``path`` parameters
203-
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
204-
205-
If possible, plugins with custom items should use :ref:`cooperative
206-
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
207-
arguments they only pass on to the superclass.
208-
209-
.. note::
210-
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
211-
new attribute being ``path``) is **the opposite** of the situation for
212-
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (the old
213-
argument being ``path``).
214-
215-
This is an unfortunate artifact due to historical reasons, which should be
216-
resolved in future versions as we slowly get rid of the :pypi:`py`
217-
dependency (see :issue:`9283` for a longer discussion).
218-
219-
Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
220-
which still is expected to return a ``py.path.local`` object, nodes still have
221-
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
222-
no matter what argument was used in the constructor. We expect to deprecate the
223-
``fspath`` attribute in a future release.
224-
225-
226186
Configuring hook specs/impls using markers
227187
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
228188

@@ -353,6 +313,48 @@ an appropriate period of deprecation has passed.
353313

354314
Some breaking changes which could not be deprecated are also listed.
355315

316+
317+
.. _node-ctor-fspath-deprecation:
318+
319+
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
320+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
321+
322+
.. deprecated:: 7.0
323+
.. versionremoved:: 9.1
324+
325+
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
326+
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
327+
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
328+
is now deprecated.
329+
330+
Plugins which construct nodes should pass the ``path`` argument, of type
331+
:class:`pathlib.Path`, instead of the ``fspath`` argument.
332+
333+
Plugins which implement custom items and collectors are encouraged to replace
334+
``fspath`` parameters (``py.path.local``) with ``path`` parameters
335+
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
336+
337+
If possible, plugins with custom items should use :ref:`cooperative
338+
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
339+
arguments they only pass on to the superclass.
340+
341+
.. note::
342+
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
343+
new attribute being ``path``) is **the opposite** of the situation for
344+
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (the old
345+
argument being ``path``).
346+
347+
This is an unfortunate artifact due to historical reasons, which should be
348+
resolved in future versions as we slowly get rid of the :pypi:`py`
349+
dependency (see :issue:`9283` for a longer discussion).
350+
351+
Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
352+
which still is expected to return a ``py.path.local`` object, nodes still have
353+
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
354+
no matter what argument was used in the constructor. We expect to deprecate the
355+
``fspath`` attribute in a future release.
356+
357+
356358
.. _sync-test-async-fixture:
357359

358360
sync test depending on async fixture

src/_pytest/config/compat.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/_pytest/deprecated.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from warnings import warn
1515

1616
from _pytest.warning_types import PytestDeprecationWarning
17-
from _pytest.warning_types import PytestRemovedIn9Warning
1817
from _pytest.warning_types import PytestRemovedIn10Warning
1918
from _pytest.warning_types import UnformattedWarning
2019

@@ -39,14 +38,6 @@
3938
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
4039

4140

42-
NODE_CTOR_FSPATH_ARG = UnformattedWarning(
43-
PytestRemovedIn9Warning,
44-
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. "
45-
"Please use the (path: pathlib.Path) argument instead.\n"
46-
"See https://docs.pytest.org/en/latest/deprecations.html"
47-
"#fspath-argument-for-node-constructors-replaced-with-pathlib-path",
48-
)
49-
5041
HOOK_LEGACY_MARKING = UnformattedWarning(
5142
PytestDeprecationWarning,
5243
"The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n"

src/_pytest/nodes.py

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
from _pytest.compat import signature
3232
from _pytest.config import Config
3333
from _pytest.config import ConftestImportFailure
34-
from _pytest.config.compat import _check_path
35-
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
3634
from _pytest.mark.structures import Mark
3735
from _pytest.mark.structures import MarkDecorator
3836
from _pytest.mark.structures import NodeKeywords
@@ -71,27 +69,6 @@ def norm_sep(path: str | os.PathLike[str]) -> str:
7169
_T = TypeVar("_T")
7270

7371

74-
def _imply_path(
75-
node_type: type[Node],
76-
path: Path | None,
77-
fspath: LEGACY_PATH | None,
78-
) -> Path:
79-
if fspath is not None:
80-
warnings.warn(
81-
NODE_CTOR_FSPATH_ARG.format(
82-
node_type_name=node_type.__name__,
83-
),
84-
stacklevel=6,
85-
)
86-
if path is not None:
87-
if fspath is not None:
88-
_check_path(path, fspath)
89-
return path
90-
else:
91-
assert fspath is not None
92-
return Path(fspath)
93-
94-
9572
_NodeType = TypeVar("_NodeType", bound="Node")
9673

9774

@@ -173,7 +150,7 @@ def __init__(
173150
parent: Node | None = None,
174151
config: Config | None = None,
175152
session: Session | None = None,
176-
fspath: LEGACY_PATH | None = None,
153+
fspath: None = None,
177154
path: Path | None = None,
178155
nodeid: str | None = None,
179156
) -> None:
@@ -199,10 +176,11 @@ def __init__(
199176
raise TypeError("session or parent must be provided")
200177
self.session = parent.session
201178

202-
if path is None and fspath is None:
203-
path = getattr(parent, "path", None)
204-
#: Filesystem path where this node was collected from (can be None).
205-
self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath)
179+
if path is None:
180+
assert parent is not None
181+
path = parent.path
182+
#: Filesystem path where this node was collected from.
183+
self.path: pathlib.Path = path
206184

207185
# The explicit annotation is to avoid publicly exposing NodeKeywords.
208186
#: Keywords/markers collected from all scopes.
@@ -248,7 +226,7 @@ def from_parent(cls, parent: Node, **kw) -> Self:
248226

249227
@property
250228
def ihook(self) -> pluggy.HookRelay:
251-
"""fspath-sensitive hook proxy used to call pytest hooks."""
229+
"""Path-sensitive hook proxy used to call pytest hooks."""
252230
return self.session.gethookproxy(self.path)
253231

254232
def __repr__(self) -> str:
@@ -576,7 +554,7 @@ class FSCollector(Collector, abc.ABC):
576554

577555
def __init__(
578556
self,
579-
fspath: LEGACY_PATH | None = None,
557+
fspath: None = None,
580558
path_or_parent: Path | Node | None = None,
581559
path: Path | None = None,
582560
name: str | None = None,
@@ -592,8 +570,8 @@ def __init__(
592570
elif isinstance(path_or_parent, Path):
593571
assert path is None
594572
path = path_or_parent
573+
assert path is not None
595574

596-
path = _imply_path(type(self), path, fspath=fspath)
597575
if name is None:
598576
name = path.name
599577
if parent is not None and parent.path != path:
@@ -633,7 +611,7 @@ def from_parent(
633611
cls,
634612
parent,
635613
*,
636-
fspath: LEGACY_PATH | None = None,
614+
fspath: None = None,
637615
path: Path | None = None,
638616
**kw,
639617
) -> Self:

src/_pytest/python.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
from _pytest.compat import get_real_func
4646
from _pytest.compat import getimfunc
4747
from _pytest.compat import is_async_function
48-
from _pytest.compat import LEGACY_PATH
4948
from _pytest.compat import NOTSET
5049
from _pytest.compat import safe_getattr
5150
from _pytest.compat import safe_isclass
@@ -654,7 +653,7 @@ class Package(nodes.Directory):
654653

655654
def __init__(
656655
self,
657-
fspath: LEGACY_PATH | None,
656+
fspath: None,
658657
parent: nodes.Collector,
659658
# NOTE: following args are unused:
660659
config=None,

testing/deprecated_test.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# mypy: allow-untyped-defs
22
from __future__ import annotations
33

4-
import re
5-
64
from _pytest import deprecated
7-
from _pytest.compat import legacy_path
85
from _pytest.pytester import Pytester
96
import pytest
107
from pytest import PytestDeprecationWarning
@@ -88,22 +85,3 @@ def __init__(self, foo: int, *, _ispytest: bool = False) -> None:
8885

8986
# Doesn't warn.
9087
PrivateInit(10, _ispytest=True)
91-
92-
93-
def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None:
94-
mod = pytester.getmodulecol("")
95-
96-
class MyFile(pytest.File):
97-
def collect(self):
98-
raise NotImplementedError()
99-
100-
with pytest.warns(
101-
pytest.PytestDeprecationWarning,
102-
match=re.escape(
103-
"The (fspath: py.path.local) argument to MyFile is deprecated."
104-
),
105-
):
106-
MyFile.from_parent(
107-
parent=mod.parent,
108-
fspath=legacy_path("bla"),
109-
)

testing/test_nodes.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import warnings
77

88
from _pytest import nodes
9-
from _pytest.compat import legacy_path
109
from _pytest.outcomes import OutcomeException
1110
from _pytest.pytester import Pytester
1211
from _pytest.warning_types import PytestWarning
@@ -36,18 +35,14 @@ def test_node_direct_construction_deprecated() -> None:
3635
def test_subclassing_both_item_and_collector_deprecated(
3736
request, tmp_path: Path
3837
) -> None:
39-
"""
40-
Verifies we warn on diamond inheritance as well as correctly managing legacy
41-
inheritance constructors with missing args as found in plugins.
42-
"""
43-
# We do not expect any warnings messages to issued during class definition.
38+
"""Verifies we warn on diamond inheritance from both Item and Collector."""
39+
# We do not expect any warnings messages to be issued during class definition.
4440
with warnings.catch_warnings():
4541
warnings.simplefilter("error")
4642

4743
class SoWrong(nodes.Item, nodes.File):
48-
def __init__(self, fspath, parent):
49-
"""Legacy ctor with legacy call # don't wana see"""
50-
super().__init__(fspath, parent)
44+
def __init__(self, parent: nodes.Collector, path: Path) -> None:
45+
super().__init__(name="broken", parent=parent, path=path)
5146

5247
def collect(self):
5348
raise NotImplementedError()
@@ -56,9 +51,7 @@ def runtest(self):
5651
raise NotImplementedError()
5752

5853
with pytest.warns(PytestWarning) as rec:
59-
SoWrong.from_parent(
60-
request.session, fspath=legacy_path(tmp_path / "broken.txt")
61-
)
54+
SoWrong.from_parent(request.session, path=tmp_path / "broken.txt")
6255
messages = [str(x.message) for x in rec]
6356
assert any(
6457
re.search(".*SoWrong.* not using a cooperative constructor.*", x)

0 commit comments

Comments
 (0)