Skip to content

Commit 66a4f65

Browse files
committed
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.
1 parent 2cb34a9 commit 66a4f65

File tree

6 files changed

+76
-25
lines changed

6 files changed

+76
-25
lines changed

changelog/8144.feature.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
2+
3+
- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
4+
- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
5+
- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
6+
- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter).
7+
- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter).

src/_pytest/hookspec.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Hook specifications for pytest plugins which are invoked by pytest itself
22
and by builtin plugins."""
3+
from pathlib import Path
34
from typing import Any
45
from typing import Dict
56
from typing import List
@@ -261,27 +262,39 @@ def pytest_collection_finish(session: "Session") -> None:
261262

262263

263264
@hookspec(firstresult=True)
264-
def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[bool]:
265+
def pytest_ignore_collect(
266+
fspath: Path, path: py.path.local, config: "Config"
267+
) -> Optional[bool]:
265268
"""Return True to prevent considering this path for collection.
266269
267270
This hook is consulted for all files and directories prior to calling
268271
more specific hooks.
269272
270273
Stops at first non-None result, see :ref:`firstresult`.
271274
275+
:param pathlib.Path fspath: The path to analyze.
272276
:param py.path.local path: The path to analyze.
273277
:param _pytest.config.Config config: The pytest config object.
278+
279+
.. versionchanged:: 6.3.0
280+
The ``fspath`` parameter was added as a pathlib.Path equivalent
281+
of the ``path`` parameter.
274282
"""
275283

276284

277285
def pytest_collect_file(
278-
path: py.path.local, parent: "Collector"
286+
fspath: Path, path: py.path.local, parent: "Collector"
279287
) -> "Optional[Collector]":
280288
"""Create a Collector for the given path, or None if not relevant.
281289
282290
The new node needs to have the specified ``parent`` as a parent.
283291
292+
:param pathlib.Path fspath: The path to analyze.
284293
:param py.path.local path: The path to collect.
294+
295+
.. versionchanged:: 6.3.0
296+
The ``fspath`` parameter was added as a pathlib.Path equivalent
297+
of the ``path`` parameter.
285298
"""
286299

287300

@@ -321,7 +334,9 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
321334

322335

323336
@hookspec(firstresult=True)
324-
def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module"]:
337+
def pytest_pycollect_makemodule(
338+
fspath: Path, path: py.path.local, parent
339+
) -> Optional["Module"]:
325340
"""Return a Module collector or None for the given path.
326341
327342
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
330345
331346
Stops at first non-None result, see :ref:`firstresult`.
332347
333-
:param py.path.local path: The path of module to collect.
348+
:param pathlib.Path fspath: The path of the module to collect.
349+
:param py.path.local path: The path of the module to collect.
350+
351+
.. versionchanged:: 6.3.0
352+
The ``fspath`` parameter was added as a pathlib.Path equivalent
353+
of the ``path`` parameter.
334354
"""
335355

336356

@@ -653,11 +673,12 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
653673

654674

655675
def pytest_report_header(
656-
config: "Config", startdir: py.path.local
676+
config: "Config", startpath: Path, startdir: py.path.local
657677
) -> Union[str, List[str]]:
658678
"""Return a string or list of strings to be displayed as header info for terminal reporting.
659679
660680
:param _pytest.config.Config config: The pytest config object.
681+
:param Path startpath: The starting dir.
661682
:param py.path.local startdir: The starting dir.
662683
663684
.. note::
@@ -672,11 +693,15 @@ def pytest_report_header(
672693
This function should be implemented only in plugins or ``conftest.py``
673694
files situated at the tests root directory due to how pytest
674695
:ref:`discovers plugins during startup <pluginorder>`.
696+
697+
.. versionchanged:: 6.3.0
698+
The ``startpath`` parameter was added as a pathlib.Path equivalent
699+
of the ``startdir`` parameter.
675700
"""
676701

677702

678703
def pytest_report_collectionfinish(
679-
config: "Config", startdir: py.path.local, items: Sequence["Item"],
704+
config: "Config", startpath: Path, startdir: py.path.local, items: Sequence["Item"],
680705
) -> Union[str, List[str]]:
681706
"""Return a string or list of strings to be displayed after collection
682707
has finished successfully.
@@ -686,6 +711,7 @@ def pytest_report_collectionfinish(
686711
.. versionadded:: 3.2
687712
688713
:param _pytest.config.Config config: The pytest config object.
714+
:param Path startpath: The starting path.
689715
:param py.path.local startdir: The starting dir.
690716
:param items: List of pytest items that are going to be executed; this list should not be modified.
691717
@@ -695,6 +721,10 @@ def pytest_report_collectionfinish(
695721
ran before it.
696722
If you want to have your line(s) displayed first, use
697723
:ref:`trylast=True <plugin-hookorder>`.
724+
725+
.. versionchanged:: 6.3.0
726+
The ``startpath`` parameter was added as a pathlib.Path equivalent
727+
of the ``startdir`` parameter.
698728
"""
699729

700730

src/_pytest/main.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,9 +532,10 @@ def gethookproxy(self, fspath: "os.PathLike[str]"):
532532
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
533533
if direntry.name == "__pycache__":
534534
return False
535-
path = py.path.local(direntry.path)
536-
ihook = self.gethookproxy(path.dirpath())
537-
if ihook.pytest_ignore_collect(path=path, config=self.config):
535+
fspath = Path(direntry.path)
536+
path = py.path.local(fspath)
537+
ihook = self.gethookproxy(fspath.parent)
538+
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
538539
return False
539540
norecursepatterns = self.config.getini("norecursedirs")
540541
if any(path.check(fnmatch=pat) for pat in norecursepatterns):
@@ -544,14 +545,17 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
544545
def _collectfile(
545546
self, path: py.path.local, handle_dupes: bool = True
546547
) -> Sequence[nodes.Collector]:
548+
fspath = Path(path)
547549
assert (
548550
path.isfile()
549551
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
550552
path, path.isdir(), path.exists(), path.islink()
551553
)
552554
ihook = self.gethookproxy(path)
553555
if not self.isinitpath(path):
554-
if ihook.pytest_ignore_collect(path=path, config=self.config):
556+
if ihook.pytest_ignore_collect(
557+
fspath=fspath, path=path, config=self.config
558+
):
555559
return ()
556560

557561
if handle_dupes:
@@ -563,7 +567,7 @@ def _collectfile(
563567
else:
564568
duplicate_paths.add(path)
565569

566-
return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return]
570+
return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return]
567571

568572
@overload
569573
def perform_collect(

src/_pytest/python.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from collections import Counter
1111
from collections import defaultdict
1212
from functools import partial
13+
from pathlib import Path
1314
from typing import Any
1415
from typing import Callable
1516
from typing import Dict
@@ -187,17 +188,19 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
187188

188189

189190
def pytest_collect_file(
190-
path: py.path.local, parent: nodes.Collector
191+
fspath: Path, path: py.path.local, parent: nodes.Collector
191192
) -> Optional["Module"]:
192193
ext = path.ext
193194
if ext == ".py":
194-
if not parent.session.isinitpath(path):
195+
if not parent.session.isinitpath(fspath):
195196
if not path_matches_patterns(
196197
path, parent.config.getini("python_files") + ["__init__.py"]
197198
):
198199
return None
199-
ihook = parent.session.gethookproxy(path)
200-
module: Module = ihook.pytest_pycollect_makemodule(path=path, parent=parent)
200+
ihook = parent.session.gethookproxy(fspath)
201+
module: Module = ihook.pytest_pycollect_makemodule(
202+
fspath=fspath, path=path, parent=parent
203+
)
201204
return module
202205
return None
203206

@@ -664,9 +667,10 @@ def isinitpath(self, path: py.path.local) -> bool:
664667
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
665668
if direntry.name == "__pycache__":
666669
return False
667-
path = py.path.local(direntry.path)
668-
ihook = self.session.gethookproxy(path.dirpath())
669-
if ihook.pytest_ignore_collect(path=path, config=self.config):
670+
fspath = Path(direntry.path)
671+
path = py.path.local(fspath)
672+
ihook = self.session.gethookproxy(fspath.parent)
673+
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
670674
return False
671675
norecursepatterns = self.config.getini("norecursedirs")
672676
if any(path.check(fnmatch=pat) for pat in norecursepatterns):
@@ -676,14 +680,17 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
676680
def _collectfile(
677681
self, path: py.path.local, handle_dupes: bool = True
678682
) -> Sequence[nodes.Collector]:
683+
fspath = Path(path)
679684
assert (
680685
path.isfile()
681686
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
682687
path, path.isdir(), path.exists(), path.islink()
683688
)
684689
ihook = self.session.gethookproxy(path)
685690
if not self.session.isinitpath(path):
686-
if ihook.pytest_ignore_collect(path=path, config=self.config):
691+
if ihook.pytest_ignore_collect(
692+
fspath=fspath, path=path, config=self.config
693+
):
687694
return ()
688695

689696
if handle_dupes:
@@ -695,7 +702,7 @@ def _collectfile(
695702
else:
696703
duplicate_paths.add(path)
697704

698-
return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return]
705+
return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return]
699706

700707
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
701708
this_path = self.fspath.dirpath()

src/_pytest/terminal.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ def pytest_sessionstart(self, session: "Session") -> None:
710710
msg += " -- " + str(sys.executable)
711711
self.write_line(msg)
712712
lines = self.config.hook.pytest_report_header(
713-
config=self.config, startdir=self.startdir
713+
config=self.config, startpath=self.startpath, startdir=self.startdir
714714
)
715715
self._write_report_lines_from_hooks(lines)
716716

@@ -745,7 +745,10 @@ def pytest_collection_finish(self, session: "Session") -> None:
745745
self.report_collect(True)
746746

747747
lines = self.config.hook.pytest_report_collectionfinish(
748-
config=self.config, startdir=self.startdir, items=session.items
748+
config=self.config,
749+
startpath=self.startpath,
750+
startdir=self.startdir,
751+
items=session.items,
749752
)
750753
self._write_report_lines_from_hooks(lines)
751754

testing/test_terminal.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ def test_more_quiet_reporting(self, pytester: Pytester) -> None:
10101010
def test_report_collectionfinish_hook(self, pytester: Pytester, params) -> None:
10111011
pytester.makeconftest(
10121012
"""
1013-
def pytest_report_collectionfinish(config, startdir, items):
1013+
def pytest_report_collectionfinish(config, startpath, startdir, items):
10141014
return ['hello from hook: {0} items'.format(len(items))]
10151015
"""
10161016
)
@@ -1436,8 +1436,8 @@ def pytest_report_header(config):
14361436
)
14371437
pytester.mkdir("a").joinpath("conftest.py").write_text(
14381438
"""
1439-
def pytest_report_header(config, startdir):
1440-
return ["line1", str(startdir)]
1439+
def pytest_report_header(config, startdir, startpath):
1440+
return ["line1", str(startpath)]
14411441
"""
14421442
)
14431443
result = pytester.runpytest("a")

0 commit comments

Comments
 (0)