Skip to content

Commit b7fc000

Browse files
authored
Merge pull request #9232 from bluetech/deprecate-node-fspath-args
Deprecate Node constuctor fspath argument, and other small changes/fixes
2 parents 3c5c5fe + 7706fd6 commit b7fc000

File tree

14 files changed

+115
-58
lines changed

14 files changed

+115
-58
lines changed

changelog/7259.deprecation.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``py.path.local`` arguments for hooks have been deprecated. See :ref:`the deprecation note <legacy-path-hooks-deprecated>` for full details.
2+
3+
``py.path.local`` arguments to Node constructors have been deprecated. See :ref:`the deprecation note <node-ctor-fspath-deprecation>` for full details.

doc/en/deprecations.rst

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,40 @@ Deprecated Features
1818
Below is a complete list of all pytest features which are considered deprecated. Using those features will issue
1919
:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
2020

21+
.. _node-ctor-fspath-deprecation:
22+
23+
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
24+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
26+
.. deprecated:: 7.0
27+
28+
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
29+
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
30+
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
31+
is now deprecated.
32+
33+
Plugins which construct nodes should pass the ``path`` argument, of type
34+
:class:`pathlib.Path`, instead of the ``fspath`` argument.
35+
36+
Plugins which implement custom items and collectors are encouraged to replace
37+
``py.path.local`` ``fspath`` parameters with ``pathlib.Path`` parameters, and
38+
drop any other usage of the ``py`` library if possible.
39+
40+
41+
.. _legacy-path-hooks-deprecated:
2142

2243
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
2344
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2445

25-
In order to support the transition to :mod:`pathlib`, the following hooks now receive additional arguments:
46+
.. deprecated:: 7.0
47+
48+
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
2649

27-
* :func:`pytest_ignore_collect(fspath: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>`
28-
* :func:`pytest_collect_file(fspath: pathlib.Path) <_pytest.hookspec.pytest_collect_file>`
29-
* :func:`pytest_pycollect_makemodule(fspath: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>`
30-
* :func:`pytest_report_header(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_header>`
31-
* :func:`pytest_report_collectionfinish(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>`
50+
* :func:`pytest_ignore_collect(fspath: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>` instead of ``path``
51+
* :func:`pytest_collect_file(fspath: pathlib.Path) <_pytest.hookspec.pytest_collect_file>` instead of ``path``
52+
* :func:`pytest_pycollect_makemodule(fspath: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>` instead of ``path``
53+
* :func:`pytest_report_header(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_header>` instead of ``startdir``
54+
* :func:`pytest_report_collectionfinish(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>` instead of ``startdir``
3255

3356
The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
3457

@@ -59,7 +82,7 @@ Implement the :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load
5982
Diamond inheritance between :class:`pytest.File` and :class:`pytest.Item`
6083
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6184

62-
.. deprecated:: 6.3
85+
.. deprecated:: 7.0
6386

6487
Inheriting from both Item and file at once has never been supported officially,
6588
however some plugins providing linting/code analysis have been using this as a hack.
@@ -86,7 +109,7 @@ scheduled for removal in pytest 7 (deprecated since pytest 2.4.0):
86109
Raising ``unittest.SkipTest`` during collection
87110
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88111

89-
.. deprecated:: 6.3
112+
.. deprecated:: 7.0
90113

91114
Raising :class:`unittest.SkipTest` to skip collection of tests during the
92115
pytest collection phase is deprecated. Use :func:`pytest.skip` instead.

doc/en/how-to/capture-warnings.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ argument ``match`` to assert that the exception matches a text or regex::
268268
... warnings.warn("this is not here", UserWarning)
269269
Traceback (most recent call last):
270270
...
271-
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
271+
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
272272

273273
You can also call :func:`pytest.warns` on a function or code string:
274274

src/_pytest/cacheprovider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def mkdir(self, name: str) -> Path:
128128
it to manage files to e.g. store/retrieve database dumps across test
129129
sessions.
130130
131-
.. versionadded:: 6.3
131+
.. versionadded:: 7.0
132132
133133
:param name:
134134
Must be a string not containing a ``/`` separator.

src/_pytest/config/argparsing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def addini(
185185
* ``paths``: a list of :class:`pathlib.Path`, separated as in a shell
186186
* ``pathlist``: a list of ``py.path``, separated as in a shell
187187
188-
.. versionadded:: 6.3
188+
.. versionadded:: 7.0
189189
The ``paths`` variable type.
190190
191191
Defaults to ``string`` if ``None`` or not passed.

src/_pytest/config/compat.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from typing import Optional
55

66
from ..compat import LEGACY_PATH
7+
from ..compat import legacy_path
78
from ..deprecated import HOOK_LEGACY_PATH_ARG
8-
from _pytest.nodes import _imply_path
9+
from _pytest.nodes import _check_path
910

1011
# hookname: (Path, LEGACY_PATH)
1112
imply_paths_hooks = {
@@ -52,7 +53,15 @@ def fixed_hook(**kw):
5253
),
5354
stacklevel=2,
5455
)
55-
path_value, fspath_value = _imply_path(path_value, fspath_value)
56+
if path_value is not None:
57+
if fspath_value is not None:
58+
_check_path(path_value, fspath_value)
59+
else:
60+
fspath_value = legacy_path(path_value)
61+
else:
62+
assert fspath_value is not None
63+
path_value = Path(fspath_value)
64+
5665
kw[path_var] = path_value
5766
kw[fspath_var] = fspath_value
5867
return hook(**kw)

src/_pytest/deprecated.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@
101101
"#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
102102
)
103103

104+
NODE_CTOR_FSPATH_ARG = UnformattedWarning(
105+
PytestDeprecationWarning,
106+
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. "
107+
"Please use the (path: pathlib.Path) argument instead.\n"
108+
"See https://docs.pytest.org/en/latest/deprecations.html"
109+
"#fspath-argument-for-node-constructors-replaced-with-pathlib-path",
110+
)
111+
104112
WARNS_NONE_ARG = PytestDeprecationWarning(
105113
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
106114
" Replace pytest.warns(None) by simply pytest.warns()."

src/_pytest/hookspec.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,13 @@ def pytest_ignore_collect(
272272
Stops at first non-None result, see :ref:`firstresult`.
273273
274274
:param pathlib.Path fspath: The path to analyze.
275-
:param LEGACY_PATH path: The path to analyze.
275+
:param LEGACY_PATH path: The path to analyze (deprecated).
276276
:param pytest.Config config: The pytest config object.
277277
278-
.. versionchanged:: 6.3.0
278+
.. versionchanged:: 7.0.0
279279
The ``fspath`` parameter was added as a :class:`pathlib.Path`
280-
equivalent of the ``path`` parameter.
280+
equivalent of the ``path`` parameter. The ``path`` parameter
281+
has been deprecated.
281282
"""
282283

283284

@@ -289,11 +290,12 @@ def pytest_collect_file(
289290
The new node needs to have the specified ``parent`` as a parent.
290291
291292
:param pathlib.Path fspath: The path to analyze.
292-
:param LEGACY_PATH path: The path to collect.
293+
:param LEGACY_PATH path: The path to collect (deprecated).
293294
294-
.. versionchanged:: 6.3.0
295+
.. versionchanged:: 7.0.0
295296
The ``fspath`` parameter was added as a :class:`pathlib.Path`
296-
equivalent of the ``path`` parameter.
297+
equivalent of the ``path`` parameter. The ``path`` parameter
298+
has been deprecated.
297299
"""
298300

299301

@@ -345,11 +347,13 @@ def pytest_pycollect_makemodule(
345347
Stops at first non-None result, see :ref:`firstresult`.
346348
347349
:param pathlib.Path fspath: The path of the module to collect.
348-
:param legacy_path path: The path of the module to collect.
350+
:param LEGACY_PATH path: The path of the module to collect (deprecated).
349351
350-
.. versionchanged:: 6.3.0
352+
.. versionchanged:: 7.0.0
351353
The ``fspath`` parameter was added as a :class:`pathlib.Path`
352354
equivalent of the ``path`` parameter.
355+
356+
The ``path`` parameter has been deprecated in favor of ``fspath``.
353357
"""
354358

355359

@@ -674,7 +678,7 @@ def pytest_report_header(
674678
675679
:param pytest.Config config: The pytest config object.
676680
:param Path startpath: The starting dir.
677-
:param LEGACY_PATH startdir: The starting dir.
681+
:param LEGACY_PATH startdir: The starting dir (deprecated).
678682
679683
.. note::
680684
@@ -689,9 +693,10 @@ def pytest_report_header(
689693
files situated at the tests root directory due to how pytest
690694
:ref:`discovers plugins during startup <pluginorder>`.
691695
692-
.. versionchanged:: 6.3.0
696+
.. versionchanged:: 7.0.0
693697
The ``startpath`` parameter was added as a :class:`pathlib.Path`
694-
equivalent of the ``startdir`` parameter.
698+
equivalent of the ``startdir`` parameter. The ``startdir`` parameter
699+
has been deprecated.
695700
"""
696701

697702

@@ -709,8 +714,8 @@ def pytest_report_collectionfinish(
709714
.. versionadded:: 3.2
710715
711716
:param pytest.Config config: The pytest config object.
712-
:param Path startpath: The starting path.
713-
:param LEGACY_PATH startdir: The starting dir.
717+
:param Path startpath: The starting dir.
718+
:param LEGACY_PATH startdir: The starting dir (deprecated).
714719
:param items: List of pytest items that are going to be executed; this list should not be modified.
715720
716721
.. note::
@@ -720,9 +725,10 @@ def pytest_report_collectionfinish(
720725
If you want to have your line(s) displayed first, use
721726
:ref:`trylast=True <plugin-hookorder>`.
722727
723-
.. versionchanged:: 6.3.0
728+
.. versionchanged:: 7.0.0
724729
The ``startpath`` parameter was added as a :class:`pathlib.Path`
725-
equivalent of the ``startdir`` parameter.
730+
equivalent of the ``startdir`` parameter. The ``startdir`` parameter
731+
has been deprecated.
726732
"""
727733

728734

src/_pytest/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ def __repr__(self) -> str:
500500
def startpath(self) -> Path:
501501
"""The path from which pytest was invoked.
502502
503-
.. versionadded:: 6.3.0
503+
.. versionadded:: 7.0.0
504504
"""
505505
return self.config.invocation_params.dir
506506

src/_pytest/nodes.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from _pytest.config import Config
2929
from _pytest.config import ConftestImportFailure
3030
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
31+
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
3132
from _pytest.mark.structures import Mark
3233
from _pytest.mark.structures import MarkDecorator
3334
from _pytest.mark.structures import NodeKeywords
@@ -102,22 +103,17 @@ def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
102103

103104

104105
def _imply_path(
105-
path: Optional[Path], fspath: Optional[LEGACY_PATH]
106-
) -> Tuple[Path, LEGACY_PATH]:
107-
if path is not None:
108-
if fspath is not None:
109-
_check_path(path, fspath)
110-
else:
111-
fspath = legacy_path(path)
112-
return path, fspath
113-
else:
114-
assert fspath is not None
115-
return Path(fspath), fspath
116-
117-
118-
# Optimization: use _imply_path_only over _imply_path when only need Path.
119-
# This is to avoid `legacy_path(path)` which is surprisingly heavy.
120-
def _imply_path_only(path: Optional[Path], fspath: Optional[LEGACY_PATH]) -> Path:
106+
node_type: Type["Node"],
107+
path: Optional[Path],
108+
fspath: Optional[LEGACY_PATH],
109+
) -> Path:
110+
if fspath is not None:
111+
warnings.warn(
112+
NODE_CTOR_FSPATH_ARG.format(
113+
node_type_name=node_type.__name__,
114+
),
115+
stacklevel=3,
116+
)
121117
if path is not None:
122118
if fspath is not None:
123119
_check_path(path, fspath)
@@ -210,9 +206,9 @@ def __init__(
210206
self.session = parent.session
211207

212208
#: Filesystem path where this node was collected from (can be None).
213-
self.path = _imply_path_only(
214-
path or getattr(parent, "path", None), fspath=fspath
215-
)
209+
if path is None and fspath is None:
210+
path = getattr(parent, "path", None)
211+
self.path = _imply_path(type(self), path, fspath=fspath)
216212

217213
# The explicit annotation is to avoid publicly exposing NodeKeywords.
218214
#: Keywords/markers collected from all scopes.
@@ -589,7 +585,7 @@ def __init__(
589585
assert path is None
590586
path = path_or_parent
591587

592-
path = _imply_path_only(path, fspath=fspath)
588+
path = _imply_path(type(self), path, fspath=fspath)
593589
if name is None:
594590
name = path.name
595591
if parent is not None and parent.path != path:
@@ -634,7 +630,6 @@ def from_parent(
634630
**kw,
635631
):
636632
"""The public constructor."""
637-
path, fspath = _imply_path(path, fspath=fspath)
638633
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
639634

640635
def gethookproxy(self, fspath: "os.PathLike[str]"):

0 commit comments

Comments
 (0)