Skip to content

Commit a6f3ec7

Browse files
committed
Remove legacy py.path argument support in hooks
Deprecated feature scheduled for removal in pytest 9. Part of #13893.
1 parent 9668d3b commit a6f3ec7

File tree

7 files changed

+43
-231
lines changed

7 files changed

+43
-231
lines changed

doc/en/deprecations.rst

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -297,33 +297,6 @@ Changed ``hookwrapper`` attributes:
297297
* ``historic``
298298

299299

300-
.. _legacy-path-hooks-deprecated:
301-
302-
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
303-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
304-
305-
.. deprecated:: 7.0
306-
307-
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
308-
309-
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
310-
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
311-
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
312-
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
313-
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
314-
315-
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.
316-
317-
.. note::
318-
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
319-
:ref:`outlined above <node-ctor-fspath-deprecation>` (the new attribute
320-
being ``path``) is **the opposite** of the situation for hooks (the old
321-
argument being ``path``).
322-
323-
This is an unfortunate artifact due to historical reasons, which should be
324-
resolved in future versions as we slowly get rid of the :pypi:`py`
325-
dependency (see :issue:`9283` for a longer discussion).
326-
327300
Directly constructing internal classes
328301
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
329302

@@ -430,6 +403,34 @@ an appropriate period of deprecation has passed.
430403

431404
Some breaking changes which could not be deprecated are also listed.
432405

406+
.. _legacy-path-hooks-deprecated:
407+
408+
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
409+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
410+
411+
.. deprecated:: 7.0
412+
.. versionremoved:: 9.0
413+
414+
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
415+
416+
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
417+
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
418+
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
419+
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
420+
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
421+
422+
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.
423+
424+
.. note::
425+
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
426+
:ref:`outlined above <node-ctor-fspath-deprecation>` (the new attribute
427+
being ``path``) is **the opposite** of the situation for hooks (the old
428+
argument being ``path``).
429+
430+
This is an unfortunate artifact due to historical reasons, which should be
431+
resolved in future versions as we slowly get rid of the :pypi:`py`
432+
dependency (see :issue:`9283` for a longer discussion).
433+
433434
.. _yield tests deprecated:
434435

435436
``yield`` tests

src/_pytest/config/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,12 @@
3838
from typing import TYPE_CHECKING
3939
import warnings
4040

41-
import pluggy
4241
from pluggy import HookimplMarker
4342
from pluggy import HookimplOpts
4443
from pluggy import HookspecMarker
4544
from pluggy import HookspecOpts
4645
from pluggy import PluginManager
4746

48-
from .compat import PathAwareHookProxy
4947
from .exceptions import PrintHelp as PrintHelp
5048
from .exceptions import UsageError as UsageError
5149
from .findpaths import determine_setup
@@ -1087,7 +1085,7 @@ def __init__(
10871085
self._store = self.stash
10881086

10891087
self.trace = self.pluginmanager.trace.root.get("config")
1090-
self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment]
1088+
self.hook = self.pluginmanager.hook
10911089
self._inicache: dict[str, Any] = {}
10921090
self._cleanup_stack = contextlib.ExitStack()
10931091
self.pluginmanager.register(self, "pytestconfig")

src/_pytest/config/compat.py

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,8 @@
11
from __future__ import annotations
22

3-
from collections.abc import Mapping
4-
import functools
53
from pathlib import Path
6-
from typing import Any
7-
import warnings
8-
9-
import pluggy
104

115
from ..compat import LEGACY_PATH
12-
from ..compat import legacy_path
13-
from ..deprecated import HOOK_LEGACY_PATH_ARG
14-
15-
16-
# hookname: (Path, LEGACY_PATH)
17-
imply_paths_hooks: Mapping[str, tuple[str, str]] = {
18-
"pytest_ignore_collect": ("collection_path", "path"),
19-
"pytest_collect_file": ("file_path", "path"),
20-
"pytest_pycollect_makemodule": ("module_path", "path"),
21-
"pytest_report_header": ("start_path", "startdir"),
22-
"pytest_report_collectionfinish": ("start_path", "startdir"),
23-
}
246

257

268
def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
@@ -29,57 +11,3 @@ def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
2911
f"Path({fspath!r}) != {path!r}\n"
3012
"if both path and fspath are given they need to be equal"
3113
)
32-
33-
34-
class PathAwareHookProxy:
35-
"""
36-
this helper wraps around hook callers
37-
until pluggy supports fixingcalls, this one will do
38-
39-
it currently doesn't return full hook caller proxies for fixed hooks,
40-
this may have to be changed later depending on bugs
41-
"""
42-
43-
def __init__(self, hook_relay: pluggy.HookRelay) -> None:
44-
self._hook_relay = hook_relay
45-
46-
def __dir__(self) -> list[str]:
47-
return dir(self._hook_relay)
48-
49-
def __getattr__(self, key: str) -> pluggy.HookCaller:
50-
hook: pluggy.HookCaller = getattr(self._hook_relay, key)
51-
if key not in imply_paths_hooks:
52-
self.__dict__[key] = hook
53-
return hook
54-
else:
55-
path_var, fspath_var = imply_paths_hooks[key]
56-
57-
@functools.wraps(hook)
58-
def fixed_hook(**kw: Any) -> Any:
59-
path_value: Path | None = kw.pop(path_var, None)
60-
fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None)
61-
if fspath_value is not None:
62-
warnings.warn(
63-
HOOK_LEGACY_PATH_ARG.format(
64-
pylib_path_arg=fspath_var, pathlib_path_arg=path_var
65-
),
66-
stacklevel=2,
67-
)
68-
if path_value is not None:
69-
if fspath_value is not None:
70-
_check_path(path_value, fspath_value)
71-
else:
72-
fspath_value = legacy_path(path_value)
73-
else:
74-
assert fspath_value is not None
75-
path_value = Path(fspath_value)
76-
77-
kw[path_var] = path_value
78-
kw[fspath_var] = fspath_value
79-
return hook(**kw)
80-
81-
fixed_hook.name = hook.name # type: ignore[attr-defined]
82-
fixed_hook.spec = hook.spec # type: ignore[attr-defined]
83-
fixed_hook.__name__ = key
84-
self.__dict__[key] = fixed_hook
85-
return fixed_hook # type: ignore[return-value]

src/_pytest/deprecated.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@
3939
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
4040

4141

42-
HOOK_LEGACY_PATH_ARG = UnformattedWarning(
43-
PytestRemovedIn9Warning,
44-
"The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n"
45-
"see https://docs.pytest.org/en/latest/deprecations.html"
46-
"#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
47-
)
48-
4942
NODE_CTOR_FSPATH_ARG = UnformattedWarning(
5043
PytestRemovedIn9Warning,
5144
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. "

src/_pytest/hookspec.py

Lines changed: 13 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414
from pluggy import HookspecMarker
1515

16-
from .deprecated import HOOK_LEGACY_PATH_ARG
17-
1816

1917
if TYPE_CHECKING:
2018
import pdb
@@ -23,7 +21,6 @@
2321

2422
from _pytest._code.code import ExceptionInfo
2523
from _pytest._code.code import ExceptionRepr
26-
from _pytest.compat import LEGACY_PATH
2724
from _pytest.config import _PluggyPlugin
2825
from _pytest.config import Config
2926
from _pytest.config import ExitCode
@@ -302,17 +299,8 @@ def pytest_collection_finish(session: Session) -> None:
302299
"""
303300

304301

305-
@hookspec(
306-
firstresult=True,
307-
warn_on_impl_args={
308-
"path": HOOK_LEGACY_PATH_ARG.format(
309-
pylib_path_arg="path", pathlib_path_arg="collection_path"
310-
),
311-
},
312-
)
313-
def pytest_ignore_collect(
314-
collection_path: Path, path: LEGACY_PATH, config: Config
315-
) -> bool | None:
302+
@hookspec(firstresult=True)
303+
def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None:
316304
"""Return ``True`` to ignore this path for collection.
317305
318306
Return ``None`` to let other plugins ignore the path for collection.
@@ -333,7 +321,7 @@ def pytest_ignore_collect(
333321
.. versionchanged:: 7.0.0
334322
The ``collection_path`` parameter was added as a :class:`pathlib.Path`
335323
equivalent of the ``path`` parameter. The ``path`` parameter
336-
has been deprecated.
324+
has been deprecated and removed in pytest 9.0.0.
337325
338326
Use in conftest plugins
339327
=======================
@@ -375,16 +363,7 @@ def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None:
375363
"""
376364

377365

378-
@hookspec(
379-
warn_on_impl_args={
380-
"path": HOOK_LEGACY_PATH_ARG.format(
381-
pylib_path_arg="path", pathlib_path_arg="file_path"
382-
),
383-
},
384-
)
385-
def pytest_collect_file(
386-
file_path: Path, path: LEGACY_PATH, parent: Collector
387-
) -> Collector | None:
366+
def pytest_collect_file(file_path: Path, parent: Collector) -> Collector | None:
388367
"""Create a :class:`~pytest.Collector` for the given path, or None if not relevant.
389368
390369
For best results, the returned collector should be a subclass of
@@ -399,7 +378,7 @@ def pytest_collect_file(
399378
.. versionchanged:: 7.0.0
400379
The ``file_path`` parameter was added as a :class:`pathlib.Path`
401380
equivalent of the ``path`` parameter. The ``path`` parameter
402-
has been deprecated.
381+
has been deprecated and removed in pytest 9.0.0.
403382
404383
Use in conftest plugins
405384
=======================
@@ -501,17 +480,8 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport | None:
501480
# -------------------------------------------------------------------------
502481

503482

504-
@hookspec(
505-
firstresult=True,
506-
warn_on_impl_args={
507-
"path": HOOK_LEGACY_PATH_ARG.format(
508-
pylib_path_arg="path", pathlib_path_arg="module_path"
509-
),
510-
},
511-
)
512-
def pytest_pycollect_makemodule(
513-
module_path: Path, path: LEGACY_PATH, parent
514-
) -> Module | None:
483+
@hookspec(firstresult=True)
484+
def pytest_pycollect_makemodule(module_path: Path, parent) -> Module | None:
515485
"""Return a :class:`pytest.Module` collector or None for the given path.
516486
517487
This hook will be called for each matching test module path.
@@ -526,9 +496,8 @@ def pytest_pycollect_makemodule(
526496
527497
.. versionchanged:: 7.0.0
528498
The ``module_path`` parameter was added as a :class:`pathlib.Path`
529-
equivalent of the ``path`` parameter.
530-
531-
The ``path`` parameter has been deprecated in favor of ``fspath``.
499+
equivalent of the ``path`` parameter. The ``path`` parameter has been
500+
deprecated in favor of ``module_path`` and removed in pytest 9.0.0.
532501
533502
Use in conftest plugins
534503
=======================
@@ -1036,16 +1005,7 @@ def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None
10361005
# -------------------------------------------------------------------------
10371006

10381007

1039-
@hookspec(
1040-
warn_on_impl_args={
1041-
"startdir": HOOK_LEGACY_PATH_ARG.format(
1042-
pylib_path_arg="startdir", pathlib_path_arg="start_path"
1043-
),
1044-
},
1045-
)
1046-
def pytest_report_header( # type:ignore[empty-body]
1047-
config: Config, start_path: Path, startdir: LEGACY_PATH
1048-
) -> str | list[str]:
1008+
def pytest_report_header(config: Config, start_path: Path) -> str | list[str]: # type: ignore[empty-body]
10491009
"""Return a string or list of strings to be displayed as header info for terminal reporting.
10501010
10511011
:param config: The pytest config object.
@@ -1063,7 +1023,7 @@ def pytest_report_header( # type:ignore[empty-body]
10631023
.. versionchanged:: 7.0.0
10641024
The ``start_path`` parameter was added as a :class:`pathlib.Path`
10651025
equivalent of the ``startdir`` parameter. The ``startdir`` parameter
1066-
has been deprecated.
1026+
has been deprecated and removed in pytest 9.0.0.
10671027
10681028
Use in conftest plugins
10691029
=======================
@@ -1072,17 +1032,9 @@ def pytest_report_header( # type:ignore[empty-body]
10721032
"""
10731033

10741034

1075-
@hookspec(
1076-
warn_on_impl_args={
1077-
"startdir": HOOK_LEGACY_PATH_ARG.format(
1078-
pylib_path_arg="startdir", pathlib_path_arg="start_path"
1079-
),
1080-
},
1081-
)
1082-
def pytest_report_collectionfinish( # type:ignore[empty-body]
1035+
def pytest_report_collectionfinish( # type: ignore[empty-body]
10831036
config: Config,
10841037
start_path: Path,
1085-
startdir: LEGACY_PATH,
10861038
items: Sequence[Item],
10871039
) -> str | list[str]:
10881040
"""Return a string or list of strings to be displayed after collection
@@ -1108,7 +1060,7 @@ def pytest_report_collectionfinish( # type:ignore[empty-body]
11081060
.. versionchanged:: 7.0.0
11091061
The ``start_path`` parameter was added as a :class:`pathlib.Path`
11101062
equivalent of the ``startdir`` parameter. The ``startdir`` parameter
1111-
has been deprecated.
1063+
has been deprecated and removed in pytest 9.0.0.
11121064
11131065
Use in conftest plugins
11141066
=======================

src/_pytest/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
from _pytest.config import UsageError
3535
from _pytest.config.argparsing import OverrideIniAction
3636
from _pytest.config.argparsing import Parser
37-
from _pytest.config.compat import PathAwareHookProxy
3837
from _pytest.outcomes import exit
3938
from _pytest.pathlib import absolutepath
4039
from _pytest.pathlib import bestrelpath
@@ -727,7 +726,7 @@ def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
727726
proxy: pluggy.HookRelay
728727
if remove_mods:
729728
# One or more conftests are not in use at this path.
730-
proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment]
729+
proxy = FSHookProxy(pm, remove_mods) # type: ignore[assignment]
731730
else:
732731
# All plugins are active for this fspath.
733732
proxy = self.config.hook

0 commit comments

Comments
 (0)