Skip to content

Commit 6c899a0

Browse files
authored
Merge pull request #8144 from bluetech/py-to-pathlib-4
hookspec: add pathlib.Path alternatives to py.path.local parameters in hooks
2 parents 8eef8c6 + 592b32b commit 6c899a0

File tree

15 files changed

+160
-119
lines changed

15 files changed

+160
-119
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/assertion/rewrite.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
from typing import TYPE_CHECKING
2828
from typing import Union
2929

30-
import py
31-
3230
from _pytest._io.saferepr import saferepr
3331
from _pytest._version import version
3432
from _pytest.assertion import util
@@ -37,6 +35,7 @@
3735
)
3836
from _pytest.config import Config
3937
from _pytest.main import Session
38+
from _pytest.pathlib import absolutepath
4039
from _pytest.pathlib import fnmatch_ex
4140
from _pytest.store import StoreKey
4241

@@ -215,7 +214,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
215214
return True
216215

217216
if self.session is not None:
218-
if self.session.isinitpath(py.path.local(fn)):
217+
if self.session.isinitpath(absolutepath(fn)):
219218
state.trace(f"matched test file (was specified on cmdline): {fn!r}")
220219
return True
221220

src/_pytest/config/argparsing.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import argparse
2+
import os
23
import sys
34
import warnings
45
from gettext import gettext
@@ -14,8 +15,6 @@
1415
from typing import TYPE_CHECKING
1516
from typing import Union
1617

17-
import py
18-
1918
import _pytest._io
2019
from _pytest.compat import final
2120
from _pytest.config.exceptions import UsageError
@@ -97,14 +96,14 @@ def addoption(self, *opts: str, **attrs: Any) -> None:
9796

9897
def parse(
9998
self,
100-
args: Sequence[Union[str, py.path.local]],
99+
args: Sequence[Union[str, "os.PathLike[str]"]],
101100
namespace: Optional[argparse.Namespace] = None,
102101
) -> argparse.Namespace:
103102
from _pytest._argcomplete import try_argcomplete
104103

105104
self.optparser = self._getparser()
106105
try_argcomplete(self.optparser)
107-
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
106+
strargs = [os.fspath(x) for x in args]
108107
return self.optparser.parse_args(strargs, namespace=namespace)
109108

110109
def _getparser(self) -> "MyOptionParser":
@@ -128,7 +127,7 @@ def _getparser(self) -> "MyOptionParser":
128127

129128
def parse_setoption(
130129
self,
131-
args: Sequence[Union[str, py.path.local]],
130+
args: Sequence[Union[str, "os.PathLike[str]"]],
132131
option: argparse.Namespace,
133132
namespace: Optional[argparse.Namespace] = None,
134133
) -> List[str]:
@@ -139,21 +138,21 @@ def parse_setoption(
139138

140139
def parse_known_args(
141140
self,
142-
args: Sequence[Union[str, py.path.local]],
141+
args: Sequence[Union[str, "os.PathLike[str]"]],
143142
namespace: Optional[argparse.Namespace] = None,
144143
) -> argparse.Namespace:
145144
"""Parse and return a namespace object with known arguments at this point."""
146145
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
147146

148147
def parse_known_and_unknown_args(
149148
self,
150-
args: Sequence[Union[str, py.path.local]],
149+
args: Sequence[Union[str, "os.PathLike[str]"]],
151150
namespace: Optional[argparse.Namespace] = None,
152151
) -> Tuple[argparse.Namespace, List[str]]:
153152
"""Parse and return a namespace object with known arguments, and
154153
the remaining arguments unknown at this point."""
155154
optparser = self._getparser()
156-
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
155+
strargs = [os.fspath(x) for x in args]
157156
return optparser.parse_known_args(strargs, namespace=namespace)
158157

159158
def addini(

src/_pytest/fixtures.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -648,12 +648,13 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
648648
if has_params:
649649
frame = inspect.stack()[3]
650650
frameinfo = inspect.getframeinfo(frame[0])
651-
source_path = py.path.local(frameinfo.filename)
651+
source_path = absolutepath(frameinfo.filename)
652652
source_lineno = frameinfo.lineno
653-
rel_source_path = source_path.relto(funcitem.config.rootdir)
654-
if rel_source_path:
655-
source_path_str = rel_source_path
656-
else:
653+
try:
654+
source_path_str = str(
655+
source_path.relative_to(funcitem.config.rootpath)
656+
)
657+
except ValueError:
657658
source_path_str = str(source_path)
658659
msg = (
659660
"The requested fixture has no parameter defined for test:\n"
@@ -876,7 +877,7 @@ def formatrepr(self) -> "FixtureLookupErrorRepr":
876877
class FixtureLookupErrorRepr(TerminalRepr):
877878
def __init__(
878879
self,
879-
filename: Union[str, py.path.local],
880+
filename: Union[str, "os.PathLike[str]"],
880881
firstlineno: int,
881882
tblines: Sequence[str],
882883
errorstring: str,
@@ -903,7 +904,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
903904
f"{FormattedExcinfo.flow_marker} {line.strip()}", red=True,
904905
)
905906
tw.line()
906-
tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
907+
tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1))
907908

908909

909910
def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":

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 :class:`pathlib.Path`
281+
equivalent 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 :class:`pathlib.Path`
297+
equivalent 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 :class:`pathlib.Path`
353+
equivalent 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 :class:`pathlib.Path`
699+
equivalent 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 :class:`pathlib.Path`
727+
equivalent of the ``startdir`` parameter.
698728
"""
699729

700730

0 commit comments

Comments
 (0)