Skip to content

Commit 6c89f92

Browse files
committed
Remove deprecated py.path (fspath) node constructor arguments
1 parent a98f02d commit 6c89f92

12 files changed

+66
-153
lines changed

doc/en/conf.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@
199199
("py:class", "_tracing.TagTracerSub"),
200200
("py:class", "warnings.WarningMessage"),
201201
# Undocumented type aliases
202-
("py:class", "LEGACY_PATH"),
203202
("py:class", "_PluggyPlugin"),
204203
# TypeVars
205204
("py:class", "_pytest._code.code.E"),

doc/en/deprecations.rst

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,45 +19,6 @@ Below is a complete list of all pytest features which are considered deprecated.
1919
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
2020

2121

22-
.. _node-ctor-fspath-deprecation:
23-
24-
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
25-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26-
27-
.. deprecated:: 7.0
28-
29-
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
30-
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
31-
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
32-
is now deprecated.
33-
34-
Plugins which construct nodes should pass the ``path`` argument, of type
35-
:class:`pathlib.Path`, instead of the ``fspath`` argument.
36-
37-
Plugins which implement custom items and collectors are encouraged to replace
38-
``fspath`` parameters (``py.path.local``) with ``path`` parameters
39-
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
40-
41-
If possible, plugins with custom items should use :ref:`cooperative
42-
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
43-
arguments they only pass on to the superclass.
44-
45-
.. note::
46-
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
47-
new attribute being ``path``) is **the opposite** of the situation for
48-
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (the old
49-
argument being ``path``).
50-
51-
This is an unfortunate artifact due to historical reasons, which should be
52-
resolved in future versions as we slowly get rid of the :pypi:`py`
53-
dependency (see :issue:`9283` for a longer discussion).
54-
55-
Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
56-
which still is expected to return a ``py.path.local`` object, nodes still have
57-
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
58-
no matter what argument was used in the constructor. We expect to deprecate the
59-
``fspath`` attribute in a future release.
60-
6122
.. _legacy-path-hooks-deprecated:
6223

6324
Configuring hook specs/impls using markers
@@ -251,6 +212,46 @@ an appropriate period of deprecation has passed.
251212

252213
Some breaking changes which could not be deprecated are also listed.
253214

215+
.. _node-ctor-fspath-deprecation:
216+
217+
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
218+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
219+
220+
.. deprecated:: 7.0
221+
222+
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
223+
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
224+
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
225+
is now deprecated.
226+
227+
Plugins which construct nodes should pass the ``path`` argument, of type
228+
:class:`pathlib.Path`, instead of the ``fspath`` argument.
229+
230+
Plugins which implement custom items and collectors are encouraged to replace
231+
``fspath`` parameters (``py.path.local``) with ``path`` parameters
232+
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
233+
234+
If possible, plugins with custom items should use :ref:`cooperative
235+
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
236+
arguments they only pass on to the superclass.
237+
238+
.. note::
239+
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
240+
new attribute being ``path``) is **the opposite** of the situation for
241+
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (the old
242+
argument being ``path``).
243+
244+
This is an unfortunate artifact due to historical reasons, which should be
245+
resolved in future versions as we slowly get rid of the :pypi:`py`
246+
dependency (see :issue:`9283` for a longer discussion).
247+
248+
Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
249+
which still is expected to return a ``py.path.local`` object, nodes still have
250+
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
251+
no matter what argument was used in the constructor. We expect to deprecate the
252+
``fspath`` attribute in a future release.
253+
254+
254255
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
255256
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
256257

src/_pytest/compat.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,10 @@
1616
from typing import NoReturn
1717
from typing import TypeVar
1818

19-
import py
20-
2119

2220
_T = TypeVar("_T")
2321
_S = TypeVar("_S")
2422

25-
#: constant to prepare valuing pylib path replacements/lazy proxies later on
26-
# intended for removal in pytest 8.0 or 9.0
27-
28-
# fmt: off
29-
# intentional space to create a fake difference for the verification
30-
LEGACY_PATH = py.path. local
31-
# fmt: on
32-
33-
34-
def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH:
35-
"""Internal wrapper to prepare lazy proxies for legacy_path instances"""
36-
return LEGACY_PATH(path)
37-
3823

3924
# fmt: off
4025
# Singleton type for NOTSET, as described in:

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
@@ -11,7 +11,6 @@
1111
from warnings import warn
1212

1313
from _pytest.warning_types import PytestDeprecationWarning
14-
from _pytest.warning_types import PytestRemovedIn8Warning
1514
from _pytest.warning_types import PytestRemovedIn9Warning
1615
from _pytest.warning_types import UnformattedWarning
1716

@@ -35,14 +34,6 @@
3534
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
3635

3736

38-
NODE_CTOR_FSPATH_ARG = UnformattedWarning(
39-
PytestRemovedIn8Warning,
40-
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. "
41-
"Please use the (path: pathlib.Path) argument instead.\n"
42-
"See https://docs.pytest.org/en/latest/deprecations.html"
43-
"#fspath-argument-for-node-constructors-replaced-with-pathlib-path",
44-
)
45-
4637
HOOK_LEGACY_MARKING = UnformattedWarning(
4738
PytestDeprecationWarning,
4839
"The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n"

src/_pytest/legacypath.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Add backward compatibility support for the legacy py path type."""
22
import dataclasses
3+
import os
34
import shlex
45
import subprocess
56
from pathlib import Path
@@ -12,9 +13,8 @@
1213

1314
from iniconfig import SectionWrapper
1415

16+
import py
1517
from _pytest.cacheprovider import Cache
16-
from _pytest.compat import LEGACY_PATH
17-
from _pytest.compat import legacy_path
1818
from _pytest.config import Config
1919
from _pytest.config import hookimpl
2020
from _pytest.config import PytestPluginManager
@@ -36,6 +36,20 @@
3636
import pexpect
3737

3838

39+
#: constant to prepare valuing pylib path replacements/lazy proxies later on
40+
# intended for removal in pytest 8.0 or 9.0
41+
42+
# fmt: off
43+
# intentional space to create a fake difference for the verification
44+
LEGACY_PATH = py.path. local
45+
# fmt: on
46+
47+
48+
def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
49+
"""Internal wrapper to prepare lazy proxies for legacy_path instances"""
50+
return LEGACY_PATH(path)
51+
52+
3953
@final
4054
class Testdir:
4155
"""

src/_pytest/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,6 @@ def __init__(self, config: Config) -> None:
541541
super().__init__(
542542
name="",
543543
path=config.rootpath,
544-
fspath=None,
545544
parent=None,
546545
config=config,
547546
session=self,

src/_pytest/nodes.py

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import abc
22
import os
3-
import pathlib
43
import warnings
54
from functools import cached_property
65
from inspect import signature
@@ -28,11 +27,8 @@
2827
from _pytest._code.code import ExceptionInfo
2928
from _pytest._code.code import TerminalRepr
3029
from _pytest._code.code import Traceback
31-
from _pytest.compat import LEGACY_PATH
3230
from _pytest.config import Config
3331
from _pytest.config import ConftestImportFailure
34-
from _pytest.config.compat import _check_path
35-
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
3632
from _pytest.mark.structures import Mark
3733
from _pytest.mark.structures import MarkDecorator
3834
from _pytest.mark.structures import NodeKeywords
@@ -98,27 +94,6 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]:
9894
yield nodeid
9995

10096

101-
def _imply_path(
102-
node_type: Type["Node"],
103-
path: Optional[Path],
104-
fspath: Optional[LEGACY_PATH],
105-
) -> Path:
106-
if fspath is not None:
107-
warnings.warn(
108-
NODE_CTOR_FSPATH_ARG.format(
109-
node_type_name=node_type.__name__,
110-
),
111-
stacklevel=6,
112-
)
113-
if path is not None:
114-
if fspath is not None:
115-
_check_path(path, fspath)
116-
return path
117-
else:
118-
assert fspath is not None
119-
return Path(fspath)
120-
121-
12297
_NodeType = TypeVar("_NodeType", bound="Node")
12398

12499

@@ -173,14 +148,6 @@ class Node(abc.ABC, metaclass=NodeMeta):
173148
``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the
174149
leaf nodes.
175150
"""
176-
177-
# Implemented in the legacypath plugin.
178-
#: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage
179-
#: for methods not migrated to ``pathlib.Path`` yet, such as
180-
#: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
181-
#: a future release, prefer using :attr:`path` instead.
182-
fspath: LEGACY_PATH
183-
184151
# Use __slots__ to make attribute access faster.
185152
# Note that __dict__ is still available.
186153
__slots__ = (
@@ -200,7 +167,6 @@ def __init__(
200167
parent: "Optional[Node]" = None,
201168
config: Optional[Config] = None,
202169
session: "Optional[Session]" = None,
203-
fspath: Optional[LEGACY_PATH] = None,
204170
path: Optional[Path] = None,
205171
nodeid: Optional[str] = None,
206172
) -> None:
@@ -226,10 +192,11 @@ def __init__(
226192
raise TypeError("session or parent must be provided")
227193
self.session = parent.session
228194

229-
if path is None and fspath is None:
195+
if path is None:
230196
path = getattr(parent, "path", None)
197+
assert path is not None
231198
#: Filesystem path where this node was collected from (can be None).
232-
self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath)
199+
self.path = path
233200

234201
# The explicit annotation is to avoid publicly exposing NodeKeywords.
235202
#: Keywords/markers collected from all scopes.
@@ -595,7 +562,6 @@ class FSCollector(Collector, abc.ABC):
595562

596563
def __init__(
597564
self,
598-
fspath: Optional[LEGACY_PATH] = None,
599565
path_or_parent: Optional[Union[Path, Node]] = None,
600566
path: Optional[Path] = None,
601567
name: Optional[str] = None,
@@ -611,8 +577,8 @@ def __init__(
611577
elif isinstance(path_or_parent, Path):
612578
assert path is None
613579
path = path_or_parent
580+
assert path is not None
614581

615-
path = _imply_path(type(self), path, fspath=fspath)
616582
if name is None:
617583
name = path.name
618584
if parent is not None and parent.path != path:
@@ -652,12 +618,11 @@ def from_parent(
652618
cls,
653619
parent,
654620
*,
655-
fspath: Optional[LEGACY_PATH] = None,
656621
path: Optional[Path] = None,
657622
**kw,
658623
):
659624
"""The public constructor."""
660-
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
625+
return super().from_parent(parent=parent, path=path, **kw)
661626

662627

663628
class File(FSCollector, abc.ABC):

src/_pytest/python.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
from _pytest.compat import getlocation
4848
from _pytest.compat import is_async_function
4949
from _pytest.compat import is_generator
50-
from _pytest.compat import LEGACY_PATH
5150
from _pytest.compat import NOTSET
5251
from _pytest.compat import safe_getattr
5352
from _pytest.compat import safe_isclass
@@ -672,7 +671,6 @@ class Package(nodes.Directory):
672671

673672
def __init__(
674673
self,
675-
fspath: Optional[LEGACY_PATH],
676674
parent: nodes.Collector,
677675
# NOTE: following args are unused:
678676
config=None,
@@ -684,7 +682,6 @@ def __init__(
684682
# super().__init__(self, fspath, parent=parent)
685683
session = parent.session
686684
super().__init__(
687-
fspath=fspath,
688685
path=path,
689686
parent=parent,
690687
config=config,

testing/deprecated_test.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import re
2-
31
import pytest
42
from _pytest import deprecated
5-
from _pytest.compat import legacy_path
63
from _pytest.pytester import Pytester
74
from pytest import PytestDeprecationWarning
85

@@ -87,25 +84,6 @@ def __init__(self, foo: int, *, _ispytest: bool = False) -> None:
8784
PrivateInit(10, _ispytest=True)
8885

8986

90-
def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None:
91-
mod = pytester.getmodulecol("")
92-
93-
class MyFile(pytest.File):
94-
def collect(self):
95-
raise NotImplementedError()
96-
97-
with pytest.warns(
98-
pytest.PytestDeprecationWarning,
99-
match=re.escape(
100-
"The (fspath: py.path.local) argument to MyFile is deprecated."
101-
),
102-
):
103-
MyFile.from_parent(
104-
parent=mod.parent,
105-
fspath=legacy_path("bla"),
106-
)
107-
108-
10987
def test_fixture_disallow_on_marked_functions():
11088
"""Test that applying @pytest.fixture to a marked function warns (#3364)."""
11189
with pytest.warns(

0 commit comments

Comments
 (0)