hookspec: add pathlib.Path alternatives to py.path.local parameters in hooks

As part of the ongoing migration for py.path to pathlib, make sure all
hooks which take a py.path.local also take an equivalent pathlib.Path.
This commit is contained in:
Ran Benita 2020-12-14 18:16:14 +02:00
parent 2cb34a99cb
commit 592b32bd69
6 changed files with 76 additions and 25 deletions

View File

@ -0,0 +1,7 @@
The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter).
- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter).

View File

@ -1,5 +1,6 @@
"""Hook specifications for pytest plugins which are invoked by pytest itself
and by builtin plugins."""
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
@ -261,7 +262,9 @@ def pytest_collection_finish(session: "Session") -> None:
@hookspec(firstresult=True)
def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[bool]:
def pytest_ignore_collect(
fspath: Path, path: py.path.local, config: "Config"
) -> Optional[bool]:
"""Return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling
@ -269,19 +272,29 @@ def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[boo
Stops at first non-None result, see :ref:`firstresult`.
:param pathlib.Path fspath: The path to analyze.
:param py.path.local path: The path to analyze.
:param _pytest.config.Config config: The pytest config object.
.. versionchanged:: 6.3.0
The ``fspath`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``path`` parameter.
"""
def pytest_collect_file(
path: py.path.local, parent: "Collector"
fspath: Path, path: py.path.local, parent: "Collector"
) -> "Optional[Collector]":
"""Create a Collector for the given path, or None if not relevant.
The new node needs to have the specified ``parent`` as a parent.
:param pathlib.Path fspath: The path to analyze.
:param py.path.local path: The path to collect.
.. versionchanged:: 6.3.0
The ``fspath`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``path`` parameter.
"""
@ -321,7 +334,9 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
@hookspec(firstresult=True)
def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module"]:
def pytest_pycollect_makemodule(
fspath: Path, path: py.path.local, parent
) -> Optional["Module"]:
"""Return a Module collector or None for the given path.
This hook will be called for each matching test module path.
@ -330,7 +345,12 @@ def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module
Stops at first non-None result, see :ref:`firstresult`.
:param py.path.local path: The path of module to collect.
:param pathlib.Path fspath: The path of the module to collect.
:param py.path.local path: The path of the module to collect.
.. versionchanged:: 6.3.0
The ``fspath`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``path`` parameter.
"""
@ -653,11 +673,12 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
def pytest_report_header(
config: "Config", startdir: py.path.local
config: "Config", startpath: Path, startdir: py.path.local
) -> Union[str, List[str]]:
"""Return a string or list of strings to be displayed as header info for terminal reporting.
:param _pytest.config.Config config: The pytest config object.
:param Path startpath: The starting dir.
:param py.path.local startdir: The starting dir.
.. note::
@ -672,11 +693,15 @@ def pytest_report_header(
This function should be implemented only in plugins or ``conftest.py``
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
.. versionchanged:: 6.3.0
The ``startpath`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``startdir`` parameter.
"""
def pytest_report_collectionfinish(
config: "Config", startdir: py.path.local, items: Sequence["Item"],
config: "Config", startpath: Path, startdir: py.path.local, items: Sequence["Item"],
) -> Union[str, List[str]]:
"""Return a string or list of strings to be displayed after collection
has finished successfully.
@ -686,6 +711,7 @@ def pytest_report_collectionfinish(
.. versionadded:: 3.2
:param _pytest.config.Config config: The pytest config object.
:param Path startpath: The starting path.
:param py.path.local startdir: The starting dir.
:param items: List of pytest items that are going to be executed; this list should not be modified.
@ -695,6 +721,10 @@ def pytest_report_collectionfinish(
ran before it.
If you want to have your line(s) displayed first, use
:ref:`trylast=True <plugin-hookorder>`.
.. versionchanged:: 6.3.0
The ``startpath`` parameter was added as a :class:`pathlib.Path`
equivalent of the ``startdir`` parameter.
"""

View File

@ -532,9 +532,10 @@ class Session(nodes.FSCollector):
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
if direntry.name == "__pycache__":
return False
path = py.path.local(direntry.path)
ihook = self.gethookproxy(path.dirpath())
if ihook.pytest_ignore_collect(path=path, config=self.config):
fspath = Path(direntry.path)
path = py.path.local(fspath)
ihook = self.gethookproxy(fspath.parent)
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
return False
norecursepatterns = self.config.getini("norecursedirs")
if any(path.check(fnmatch=pat) for pat in norecursepatterns):
@ -544,6 +545,7 @@ class Session(nodes.FSCollector):
def _collectfile(
self, path: py.path.local, handle_dupes: bool = True
) -> Sequence[nodes.Collector]:
fspath = Path(path)
assert (
path.isfile()
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
@ -551,7 +553,9 @@ class Session(nodes.FSCollector):
)
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
if ihook.pytest_ignore_collect(
fspath=fspath, path=path, config=self.config
):
return ()
if handle_dupes:
@ -563,7 +567,7 @@ class Session(nodes.FSCollector):
else:
duplicate_paths.add(path)
return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return]
return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return]
@overload
def perform_collect(

View File

@ -10,6 +10,7 @@ import warnings
from collections import Counter
from collections import defaultdict
from functools import partial
from pathlib import Path
from typing import Any
from typing import Callable
from typing import Dict
@ -187,17 +188,19 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
def pytest_collect_file(
path: py.path.local, parent: nodes.Collector
fspath: Path, path: py.path.local, parent: nodes.Collector
) -> Optional["Module"]:
ext = path.ext
if ext == ".py":
if not parent.session.isinitpath(path):
if not parent.session.isinitpath(fspath):
if not path_matches_patterns(
path, parent.config.getini("python_files") + ["__init__.py"]
):
return None
ihook = parent.session.gethookproxy(path)
module: Module = ihook.pytest_pycollect_makemodule(path=path, parent=parent)
ihook = parent.session.gethookproxy(fspath)
module: Module = ihook.pytest_pycollect_makemodule(
fspath=fspath, path=path, parent=parent
)
return module
return None
@ -664,9 +667,10 @@ class Package(Module):
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
if direntry.name == "__pycache__":
return False
path = py.path.local(direntry.path)
ihook = self.session.gethookproxy(path.dirpath())
if ihook.pytest_ignore_collect(path=path, config=self.config):
fspath = Path(direntry.path)
path = py.path.local(fspath)
ihook = self.session.gethookproxy(fspath.parent)
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
return False
norecursepatterns = self.config.getini("norecursedirs")
if any(path.check(fnmatch=pat) for pat in norecursepatterns):
@ -676,6 +680,7 @@ class Package(Module):
def _collectfile(
self, path: py.path.local, handle_dupes: bool = True
) -> Sequence[nodes.Collector]:
fspath = Path(path)
assert (
path.isfile()
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
@ -683,7 +688,9 @@ class Package(Module):
)
ihook = self.session.gethookproxy(path)
if not self.session.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
if ihook.pytest_ignore_collect(
fspath=fspath, path=path, config=self.config
):
return ()
if handle_dupes:
@ -695,7 +702,7 @@ class Package(Module):
else:
duplicate_paths.add(path)
return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return]
return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return]
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
this_path = self.fspath.dirpath()

View File

@ -710,7 +710,7 @@ class TerminalReporter:
msg += " -- " + str(sys.executable)
self.write_line(msg)
lines = self.config.hook.pytest_report_header(
config=self.config, startdir=self.startdir
config=self.config, startpath=self.startpath, startdir=self.startdir
)
self._write_report_lines_from_hooks(lines)
@ -745,7 +745,10 @@ class TerminalReporter:
self.report_collect(True)
lines = self.config.hook.pytest_report_collectionfinish(
config=self.config, startdir=self.startdir, items=session.items
config=self.config,
startpath=self.startpath,
startdir=self.startdir,
items=session.items,
)
self._write_report_lines_from_hooks(lines)

View File

@ -1010,7 +1010,7 @@ class TestTerminalFunctional:
def test_report_collectionfinish_hook(self, pytester: Pytester, params) -> None:
pytester.makeconftest(
"""
def pytest_report_collectionfinish(config, startdir, items):
def pytest_report_collectionfinish(config, startpath, startdir, items):
return ['hello from hook: {0} items'.format(len(items))]
"""
)
@ -1436,8 +1436,8 @@ class TestGenericReporting:
)
pytester.mkdir("a").joinpath("conftest.py").write_text(
"""
def pytest_report_header(config, startdir):
return ["line1", str(startdir)]
def pytest_report_header(config, startdir, startpath):
return ["line1", str(startpath)]
"""
)
result = pytester.runpytest("a")