Remove deprecated py.path hook arguments

This commit is contained in:
Ran Benita 2024-01-01 16:36:50 +02:00
parent 0f18a7fe5e
commit a98f02d423
7 changed files with 51 additions and 161 deletions

View File

@ -104,31 +104,6 @@ Changed ``hookwrapper`` attributes:
* ``historic`` * ``historic``
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
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.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
:ref:`outlined above <node-ctor-fspath-deprecation>` (the new attribute
being ``path``) is **the opposite** of the situation for hooks (the old
argument being ``path``).
This is an unfortunate artifact due to historical reasons, which should be
resolved in future versions as we slowly get rid of the :pypi:`py`
dependency (see :issue:`9283` for a longer discussion).
Directly constructing internal classes Directly constructing internal classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -276,6 +251,33 @@ an appropriate period of deprecation has passed.
Some breaking changes which could not be deprecated are also listed. Some breaking changes which could not be deprecated are also listed.
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
.. versionremoved:: 8.0
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
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.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
:ref:`outlined above <node-ctor-fspath-deprecation>` (the new attribute
being ``path``) is **the opposite** of the situation for hooks (the old
argument being ``path``).
This is an unfortunate artifact due to historical reasons, which should be
resolved in future versions as we slowly get rid of the :pypi:`py`
dependency (see :issue:`9283` for a longer discussion).
.. _nose-deprecation: .. _nose-deprecation:
Support for tests written for nose Support for tests written for nose

View File

@ -38,7 +38,6 @@ from typing import Type
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import pluggy
from pluggy import HookimplMarker from pluggy import HookimplMarker
from pluggy import HookimplOpts from pluggy import HookimplOpts
from pluggy import HookspecMarker from pluggy import HookspecMarker
@ -48,7 +47,6 @@ from pluggy import PluginManager
import _pytest._code import _pytest._code
import _pytest.deprecated import _pytest.deprecated
import _pytest.hookspec import _pytest.hookspec
from .compat import PathAwareHookProxy
from .exceptions import PrintHelp as PrintHelp from .exceptions import PrintHelp as PrintHelp
from .exceptions import UsageError as UsageError from .exceptions import UsageError as UsageError
from .findpaths import determine_setup from .findpaths import determine_setup
@ -1008,7 +1006,7 @@ class Config:
self._store = self.stash self._store = self.stash
self.trace = self.pluginmanager.trace.root.get("config") self.trace = self.pluginmanager.trace.root.get("config")
self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] self.hook = self.pluginmanager.hook # type: ignore[assignment]
self._inicache: Dict[str, Any] = {} self._inicache: Dict[str, Any] = {}
self._override_ini: Sequence[str] = () self._override_ini: Sequence[str] = ()
self._opt2dest: Dict[str, str] = {} self._opt2dest: Dict[str, str] = {}

View File

@ -1,24 +1,8 @@
from __future__ import annotations from __future__ import annotations
import functools
import warnings
from pathlib import Path from pathlib import Path
from typing import Mapping
import pluggy
from ..compat import LEGACY_PATH from ..compat import LEGACY_PATH
from ..compat import legacy_path
from ..deprecated import HOOK_LEGACY_PATH_ARG
# hookname: (Path, LEGACY_PATH)
imply_paths_hooks: Mapping[str, tuple[str, str]] = {
"pytest_ignore_collect": ("collection_path", "path"),
"pytest_collect_file": ("file_path", "path"),
"pytest_pycollect_makemodule": ("module_path", "path"),
"pytest_report_header": ("start_path", "startdir"),
"pytest_report_collectionfinish": ("start_path", "startdir"),
}
def _check_path(path: Path, fspath: LEGACY_PATH) -> None: def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
@ -27,57 +11,3 @@ def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
f"Path({fspath!r}) != {path!r}\n" f"Path({fspath!r}) != {path!r}\n"
"if both path and fspath are given they need to be equal" "if both path and fspath are given they need to be equal"
) )
class PathAwareHookProxy:
"""
this helper wraps around hook callers
until pluggy supports fixingcalls, this one will do
it currently doesn't return full hook caller proxies for fixed hooks,
this may have to be changed later depending on bugs
"""
def __init__(self, hook_relay: pluggy.HookRelay) -> None:
self._hook_relay = hook_relay
def __dir__(self) -> list[str]:
return dir(self._hook_relay)
def __getattr__(self, key: str) -> pluggy.HookCaller:
hook: pluggy.HookCaller = getattr(self._hook_relay, key)
if key not in imply_paths_hooks:
self.__dict__[key] = hook
return hook
else:
path_var, fspath_var = imply_paths_hooks[key]
@functools.wraps(hook)
def fixed_hook(**kw):
path_value: Path | None = kw.pop(path_var, None)
fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None)
if fspath_value is not None:
warnings.warn(
HOOK_LEGACY_PATH_ARG.format(
pylib_path_arg=fspath_var, pathlib_path_arg=path_var
),
stacklevel=2,
)
if path_value is not None:
if fspath_value is not None:
_check_path(path_value, fspath_value)
else:
fspath_value = legacy_path(path_value)
else:
assert fspath_value is not None
path_value = Path(fspath_value)
kw[path_var] = path_value
kw[fspath_var] = fspath_value
return hook(**kw)
fixed_hook.name = hook.name # type: ignore[attr-defined]
fixed_hook.spec = hook.spec # type: ignore[attr-defined]
fixed_hook.__name__ = key
self.__dict__[key] = fixed_hook
return fixed_hook # type: ignore[return-value]

View File

@ -35,13 +35,6 @@ YIELD_FIXTURE = PytestDeprecationWarning(
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
HOOK_LEGACY_PATH_ARG = UnformattedWarning(
PytestRemovedIn8Warning,
"The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n"
"see https://docs.pytest.org/en/latest/deprecations.html"
"#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
)
NODE_CTOR_FSPATH_ARG = UnformattedWarning( NODE_CTOR_FSPATH_ARG = UnformattedWarning(
PytestRemovedIn8Warning, PytestRemovedIn8Warning,
"The (fspath: py.path.local) argument to {node_type_name} is deprecated. " "The (fspath: py.path.local) argument to {node_type_name} is deprecated. "

View File

@ -40,7 +40,6 @@ if TYPE_CHECKING:
from _pytest.runner import CallInfo from _pytest.runner import CallInfo
from _pytest.terminal import TerminalReporter from _pytest.terminal import TerminalReporter
from _pytest.terminal import TestShortLogReport from _pytest.terminal import TestShortLogReport
from _pytest.compat import LEGACY_PATH
hookspec = HookspecMarker("pytest") hookspec = HookspecMarker("pytest")
@ -246,9 +245,7 @@ def pytest_collection_finish(session: "Session") -> None:
@hookspec(firstresult=True) @hookspec(firstresult=True)
def pytest_ignore_collect( def pytest_ignore_collect(collection_path: Path, config: "Config") -> Optional[bool]:
collection_path: Path, path: "LEGACY_PATH", config: "Config"
) -> Optional[bool]:
"""Return True to prevent considering this path for collection. """Return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling This hook is consulted for all files and directories prior to calling
@ -262,8 +259,10 @@ def pytest_ignore_collect(
.. versionchanged:: 7.0.0 .. versionchanged:: 7.0.0
The ``collection_path`` parameter was added as a :class:`pathlib.Path` The ``collection_path`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``path`` parameter. The ``path`` parameter equivalent of the ``path`` parameter.
has been deprecated.
.. versionchanged:: 8.0.0
The ``path`` parameter has been removed.
""" """
@ -288,9 +287,7 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle
""" """
def pytest_collect_file( def pytest_collect_file(file_path: Path, parent: "Collector") -> "Optional[Collector]":
file_path: Path, path: "LEGACY_PATH", parent: "Collector"
) -> "Optional[Collector]":
"""Create a :class:`~pytest.Collector` for the given path, or None if not relevant. """Create a :class:`~pytest.Collector` for the given path, or None if not relevant.
For best results, the returned collector should be a subclass of For best results, the returned collector should be a subclass of
@ -303,8 +300,10 @@ def pytest_collect_file(
.. versionchanged:: 7.0.0 .. versionchanged:: 7.0.0
The ``file_path`` parameter was added as a :class:`pathlib.Path` The ``file_path`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``path`` parameter. The ``path`` parameter equivalent of the ``path`` parameter.
has been deprecated.
.. versionchanged:: 8.0.0
The ``path`` parameter was removed.
""" """
@ -363,9 +362,7 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
@hookspec(firstresult=True) @hookspec(firstresult=True)
def pytest_pycollect_makemodule( def pytest_pycollect_makemodule(module_path: Path, parent) -> Optional["Module"]:
module_path: Path, path: "LEGACY_PATH", parent
) -> Optional["Module"]:
"""Return a :class:`pytest.Module` collector or None for the given path. """Return a :class:`pytest.Module` collector or None for the given path.
This hook will be called for each matching test module path. This hook will be called for each matching test module path.
@ -381,7 +378,8 @@ def pytest_pycollect_makemodule(
The ``module_path`` parameter was added as a :class:`pathlib.Path` The ``module_path`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``path`` parameter. equivalent of the ``path`` parameter.
The ``path`` parameter has been deprecated in favor of ``fspath``. .. versionchanged:: 8.0.0
The ``path`` parameter has been removed in favor of ``module_path``.
""" """
@ -751,7 +749,7 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
def pytest_report_header( # type:ignore[empty-body] def pytest_report_header( # type:ignore[empty-body]
config: "Config", start_path: Path, startdir: "LEGACY_PATH" config: "Config", start_path: Path
) -> Union[str, List[str]]: ) -> Union[str, List[str]]:
"""Return a string or list of strings to be displayed as header info for terminal reporting. """Return a string or list of strings to be displayed as header info for terminal reporting.
@ -774,15 +772,16 @@ def pytest_report_header( # type:ignore[empty-body]
.. versionchanged:: 7.0.0 .. versionchanged:: 7.0.0
The ``start_path`` parameter was added as a :class:`pathlib.Path` The ``start_path`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``startdir`` parameter. The ``startdir`` parameter equivalent of the ``startdir`` parameter.
has been deprecated.
.. versionchanged:: 8.0.0
The ``startdir`` parameter has been removed.
""" """
def pytest_report_collectionfinish( # type:ignore[empty-body] def pytest_report_collectionfinish( # type:ignore[empty-body]
config: "Config", config: "Config",
start_path: Path, start_path: Path,
startdir: "LEGACY_PATH",
items: Sequence["Item"], items: Sequence["Item"],
) -> Union[str, List[str]]: ) -> Union[str, List[str]]:
"""Return a string or list of strings to be displayed after collection """Return a string or list of strings to be displayed after collection
@ -806,8 +805,10 @@ def pytest_report_collectionfinish( # type:ignore[empty-body]
.. versionchanged:: 7.0.0 .. versionchanged:: 7.0.0
The ``start_path`` parameter was added as a :class:`pathlib.Path` The ``start_path`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``startdir`` parameter. The ``startdir`` parameter equivalent of the ``startdir`` parameter.
has been deprecated.
.. versionchanged:: 8.0.0
The ``startdir`` parameter has been removed.
""" """

View File

@ -33,7 +33,6 @@ from _pytest.config import hookimpl
from _pytest.config import PytestPluginManager from _pytest.config import PytestPluginManager
from _pytest.config import UsageError from _pytest.config import UsageError
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.config.compat import PathAwareHookProxy
from _pytest.fixtures import FixtureManager from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit from _pytest.outcomes import exit
from _pytest.pathlib import absolutepath from _pytest.pathlib import absolutepath
@ -644,7 +643,7 @@ class Session(nodes.Collector):
proxy: pluggy.HookRelay proxy: pluggy.HookRelay
if remove_mods: if remove_mods:
# One or more conftests are not in use at this path. # One or more conftests are not in use at this path.
proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] proxy = FSHookProxy(pm, remove_mods) # type: ignore[arg-type,assignment]
else: else:
# All plugins are active for this fspath. # All plugins are active for this fspath.
proxy = self.config.hook proxy = self.config.hook

View File

@ -1,6 +1,4 @@
import re import re
import sys
from pathlib import Path
import pytest import pytest
from _pytest import deprecated from _pytest import deprecated
@ -89,37 +87,6 @@ def test_private_is_deprecated() -> None:
PrivateInit(10, _ispytest=True) PrivateInit(10, _ispytest=True)
@pytest.mark.parametrize("hooktype", ["hook", "ihook"])
def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request):
path = legacy_path(tmp_path)
PATH_WARN_MATCH = r".*path: py\.path\.local\) argument is deprecated, please use \(collection_path: pathlib\.Path.*"
if hooktype == "ihook":
hooks = request.node.ihook
else:
hooks = request.config.hook
with pytest.warns(PytestDeprecationWarning, match=PATH_WARN_MATCH) as r:
l1 = sys._getframe().f_lineno
hooks.pytest_ignore_collect(
config=request.config, path=path, collection_path=tmp_path
)
l2 = sys._getframe().f_lineno
(record,) = r
assert record.filename == __file__
assert l1 < record.lineno < l2
hooks.pytest_ignore_collect(config=request.config, collection_path=tmp_path)
# Passing entirely *different* paths is an outright error.
with pytest.raises(ValueError, match=r"path.*fspath.*need to be equal"):
with pytest.warns(PytestDeprecationWarning, match=PATH_WARN_MATCH) as r:
hooks.pytest_ignore_collect(
config=request.config, path=path, collection_path=Path("/bla/bla")
)
def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None:
mod = pytester.getmodulecol("") mod = pytester.getmodulecol("")