Skip to content

Commit 92ba96b

Browse files
committed
code: convert from py.path to pathlib
1 parent 7aa2240 commit 92ba96b

File tree

7 files changed

+90
-74
lines changed

7 files changed

+90
-74
lines changed

changelog/8174.trivial.rst

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The following changes have been made to internal pytest types/functions:
2+
3+
- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
4+
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
5+
- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.

src/_pytest/_code/code.py

+30-25
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
from _pytest._io.saferepr import saferepr
4444
from _pytest.compat import final
4545
from _pytest.compat import get_real_func
46+
from _pytest.pathlib import absolutepath
47+
from _pytest.pathlib import bestrelpath
4648

4749
if TYPE_CHECKING:
4850
from typing_extensions import Literal
@@ -78,16 +80,16 @@ def name(self) -> str:
7880
return self.raw.co_name
7981

8082
@property
81-
def path(self) -> Union[py.path.local, str]:
83+
def path(self) -> Union[Path, str]:
8284
"""Return a path object pointing to source code, or an ``str`` in
8385
case of ``OSError`` / non-existing file."""
8486
if not self.raw.co_filename:
8587
return ""
8688
try:
87-
p = py.path.local(self.raw.co_filename)
89+
p = absolutepath(self.raw.co_filename)
8890
# maybe don't try this checking
89-
if not p.check():
90-
raise OSError("py.path check failed.")
91+
if not p.exists():
92+
raise OSError("path check failed.")
9193
return p
9294
except OSError:
9395
# XXX maybe try harder like the weird logic
@@ -223,7 +225,7 @@ def statement(self) -> "Source":
223225
return source.getstatement(self.lineno)
224226

225227
@property
226-
def path(self) -> Union[py.path.local, str]:
228+
def path(self) -> Union[Path, str]:
227229
"""Path to the source code."""
228230
return self.frame.code.path
229231

@@ -336,10 +338,10 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]:
336338

337339
def cut(
338340
self,
339-
path=None,
341+
path: Optional[Union[Path, str]] = None,
340342
lineno: Optional[int] = None,
341343
firstlineno: Optional[int] = None,
342-
excludepath: Optional[py.path.local] = None,
344+
excludepath: Optional[Path] = None,
343345
) -> "Traceback":
344346
"""Return a Traceback instance wrapping part of this Traceback.
345347
@@ -353,17 +355,19 @@ def cut(
353355
for x in self:
354356
code = x.frame.code
355357
codepath = code.path
358+
if path is not None and codepath != path:
359+
continue
356360
if (
357-
(path is None or codepath == path)
358-
and (
359-
excludepath is None
360-
or not isinstance(codepath, py.path.local)
361-
or not codepath.relto(excludepath)
362-
)
363-
and (lineno is None or x.lineno == lineno)
364-
and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
361+
excludepath is not None
362+
and isinstance(codepath, Path)
363+
and excludepath in codepath.parents
365364
):
366-
return Traceback(x._rawentry, self._excinfo)
365+
continue
366+
if lineno is not None and x.lineno != lineno:
367+
continue
368+
if firstlineno is not None and x.frame.code.firstlineno != firstlineno:
369+
continue
370+
return Traceback(x._rawentry, self._excinfo)
367371
return self
368372

369373
@overload
@@ -801,7 +805,8 @@ def repr_traceback_entry(
801805
message = "in %s" % (entry.name)
802806
else:
803807
message = excinfo and excinfo.typename or ""
804-
path = self._makepath(entry.path)
808+
entry_path = entry.path
809+
path = self._makepath(entry_path)
805810
reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
806811
localsrepr = self.repr_locals(entry.locals)
807812
return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
@@ -814,15 +819,15 @@ def repr_traceback_entry(
814819
lines.extend(self.get_exconly(excinfo, indent=4))
815820
return ReprEntry(lines, None, None, None, style)
816821

817-
def _makepath(self, path):
818-
if not self.abspath:
822+
def _makepath(self, path: Union[Path, str]) -> str:
823+
if not self.abspath and isinstance(path, Path):
819824
try:
820-
np = py.path.local().bestrelpath(path)
825+
np = bestrelpath(Path.cwd(), path)
821826
except OSError:
822-
return path
827+
return str(path)
823828
if len(np) < len(str(path)):
824-
path = np
825-
return path
829+
return np
830+
return str(path)
826831

827832
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
828833
traceback = excinfo.traceback
@@ -1181,7 +1186,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
11811186
tw.line("")
11821187

11831188

1184-
def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
1189+
def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
11851190
"""Return source location (path, lineno) for the given object.
11861191
11871192
If the source cannot be determined return ("", -1).
@@ -1203,7 +1208,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
12031208
except TypeError:
12041209
return "", -1
12051210

1206-
fspath = fn and py.path.local(fn) or ""
1211+
fspath = fn and absolutepath(fn) or ""
12071212
lineno = -1
12081213
if fspath:
12091214
try:

src/_pytest/fixtures.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import warnings
66
from collections import defaultdict
77
from collections import deque
8+
from pathlib import Path
89
from types import TracebackType
910
from typing import Any
1011
from typing import Callable
@@ -58,6 +59,7 @@
5859
from _pytest.outcomes import fail
5960
from _pytest.outcomes import TEST_OUTCOME
6061
from _pytest.pathlib import absolutepath
62+
from _pytest.pathlib import bestrelpath
6163
from _pytest.store import StoreKey
6264

6365
if TYPE_CHECKING:
@@ -718,7 +720,11 @@ def _factorytraceback(self) -> List[str]:
718720
for fixturedef in self._get_fixturestack():
719721
factory = fixturedef.func
720722
fs, lineno = getfslineno(factory)
721-
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
723+
if isinstance(fs, Path):
724+
session: Session = self._pyfuncitem.session
725+
p = bestrelpath(Path(session.fspath), fs)
726+
else:
727+
p = fs
722728
args = _format_args(factory)
723729
lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
724730
return lines

src/_pytest/nodes.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
SEP = "/"
4141

42-
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
42+
tracebackcutdir = Path(_pytest.__file__).parent
4343

4444

4545
def iterparentnodeids(nodeid: str) -> Iterator[str]:
@@ -416,9 +416,7 @@ def repr_failure(
416416
return self._repr_failure_py(excinfo, style)
417417

418418

419-
def get_fslocation_from_item(
420-
node: "Node",
421-
) -> Tuple[Union[str, py.path.local], Optional[int]]:
419+
def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]:
422420
"""Try to extract the actual location from a node, depending on available attributes:
423421
424422
* "location": a pair (path, lineno)
@@ -474,7 +472,7 @@ def repr_failure( # type: ignore[override]
474472
def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
475473
if hasattr(self, "fspath"):
476474
traceback = excinfo.traceback
477-
ntraceback = traceback.cut(path=self.fspath)
475+
ntraceback = traceback.cut(path=Path(self.fspath))
478476
if ntraceback == traceback:
479477
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
480478
excinfo.traceback = ntraceback.filter()

src/_pytest/python.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,11 @@ def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]:
340340
fspath: Union[py.path.local, str] = file_path
341341
lineno = compat_co_firstlineno
342342
else:
343-
fspath, lineno = getfslineno(obj)
343+
path, lineno = getfslineno(obj)
344+
if isinstance(path, Path):
345+
fspath = py.path.local(path)
346+
else:
347+
fspath = path
344348
modpath = self.getmodpath()
345349
assert isinstance(lineno, int)
346350
return fspath, lineno, modpath

testing/code/test_excinfo.py

+35-36
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import importlib
22
import io
33
import operator
4-
import os
54
import queue
65
import sys
76
import textwrap
@@ -12,14 +11,14 @@
1211
from typing import TYPE_CHECKING
1312
from typing import Union
1413

15-
import py
16-
1714
import _pytest
1815
import pytest
1916
from _pytest._code.code import ExceptionChainRepr
2017
from _pytest._code.code import ExceptionInfo
2118
from _pytest._code.code import FormattedExcinfo
2219
from _pytest._io import TerminalWriter
20+
from _pytest.monkeypatch import MonkeyPatch
21+
from _pytest.pathlib import bestrelpath
2322
from _pytest.pathlib import import_path
2423
from _pytest.pytester import LineMatcher
2524
from _pytest.pytester import Pytester
@@ -150,9 +149,10 @@ def xyz():
150149
" except somenoname: # type: ignore[name-defined] # noqa: F821",
151150
]
152151

153-
def test_traceback_cut(self):
152+
def test_traceback_cut(self) -> None:
154153
co = _pytest._code.Code.from_function(f)
155154
path, firstlineno = co.path, co.firstlineno
155+
assert isinstance(path, Path)
156156
traceback = self.excinfo.traceback
157157
newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
158158
assert len(newtraceback) == 1
@@ -163,11 +163,11 @@ def test_traceback_cut_excludepath(self, pytester: Pytester) -> None:
163163
p = pytester.makepyfile("def f(): raise ValueError")
164164
with pytest.raises(ValueError) as excinfo:
165165
import_path(p).f() # type: ignore[attr-defined]
166-
basedir = py.path.local(pytest.__file__).dirpath()
166+
basedir = Path(pytest.__file__).parent
167167
newtraceback = excinfo.traceback.cut(excludepath=basedir)
168168
for x in newtraceback:
169-
if hasattr(x, "path"):
170-
assert not py.path.local(x.path).relto(basedir)
169+
assert isinstance(x.path, Path)
170+
assert basedir not in x.path.parents
171171
assert newtraceback[-1].frame.code.path == p
172172

173173
def test_traceback_filter(self):
@@ -376,7 +376,7 @@ def test_excinfo_no_python_sourcecode(tmpdir):
376376
for item in excinfo.traceback:
377377
print(item) # XXX: for some reason jinja.Template.render is printed in full
378378
item.source # shouldn't fail
379-
if isinstance(item.path, py.path.local) and item.path.basename == "test.txt":
379+
if isinstance(item.path, Path) and item.path.name == "test.txt":
380380
assert str(item.source) == "{{ h()}}:"
381381

382382

@@ -392,16 +392,16 @@ def test_entrysource_Queue_example():
392392
assert s.startswith("def get")
393393

394394

395-
def test_codepath_Queue_example():
395+
def test_codepath_Queue_example() -> None:
396396
try:
397397
queue.Queue().get(timeout=0.001)
398398
except queue.Empty:
399399
excinfo = _pytest._code.ExceptionInfo.from_current()
400400
entry = excinfo.traceback[-1]
401401
path = entry.path
402-
assert isinstance(path, py.path.local)
403-
assert path.basename.lower() == "queue.py"
404-
assert path.check()
402+
assert isinstance(path, Path)
403+
assert path.name.lower() == "queue.py"
404+
assert path.exists()
405405

406406

407407
def test_match_succeeds():
@@ -805,21 +805,21 @@ def entry():
805805

806806
raised = 0
807807

808-
orig_getcwd = os.getcwd
808+
orig_path_cwd = Path.cwd
809809

810810
def raiseos():
811811
nonlocal raised
812812
upframe = sys._getframe().f_back
813813
assert upframe is not None
814-
if upframe.f_code.co_name == "checked_call":
814+
if upframe.f_code.co_name == "_makepath":
815815
# Only raise with expected calls, but not via e.g. inspect for
816816
# py38-windows.
817817
raised += 1
818818
raise OSError(2, "custom_oserror")
819-
return orig_getcwd()
819+
return orig_path_cwd()
820820

821-
monkeypatch.setattr(os, "getcwd", raiseos)
822-
assert p._makepath(__file__) == __file__
821+
monkeypatch.setattr(Path, "cwd", raiseos)
822+
assert p._makepath(Path(__file__)) == __file__
823823
assert raised == 1
824824
repr_tb = p.repr_traceback(excinfo)
825825

@@ -1015,33 +1015,32 @@ def f():
10151015
assert line.endswith("mod.py")
10161016
assert tw_mock.lines[10] == ":3: ValueError"
10171017

1018-
def test_toterminal_long_filenames(self, importasmod, tw_mock):
1018+
def test_toterminal_long_filenames(
1019+
self, importasmod, tw_mock, monkeypatch: MonkeyPatch
1020+
) -> None:
10191021
mod = importasmod(
10201022
"""
10211023
def f():
10221024
raise ValueError()
10231025
"""
10241026
)
10251027
excinfo = pytest.raises(ValueError, mod.f)
1026-
path = py.path.local(mod.__file__)
1027-
old = path.dirpath().chdir()
1028-
try:
1029-
repr = excinfo.getrepr(abspath=False)
1030-
repr.toterminal(tw_mock)
1031-
x = py.path.local().bestrelpath(path)
1032-
if len(x) < len(str(path)):
1033-
msg = tw_mock.get_write_msg(-2)
1034-
assert msg == "mod.py"
1035-
assert tw_mock.lines[-1] == ":3: ValueError"
1036-
1037-
repr = excinfo.getrepr(abspath=True)
1038-
repr.toterminal(tw_mock)
1028+
path = Path(mod.__file__)
1029+
monkeypatch.chdir(path.parent)
1030+
repr = excinfo.getrepr(abspath=False)
1031+
repr.toterminal(tw_mock)
1032+
x = bestrelpath(Path.cwd(), path)
1033+
if len(x) < len(str(path)):
10391034
msg = tw_mock.get_write_msg(-2)
1040-
assert msg == path
1041-
line = tw_mock.lines[-1]
1042-
assert line == ":3: ValueError"
1043-
finally:
1044-
old.chdir()
1035+
assert msg == "mod.py"
1036+
assert tw_mock.lines[-1] == ":3: ValueError"
1037+
1038+
repr = excinfo.getrepr(abspath=True)
1039+
repr.toterminal(tw_mock)
1040+
msg = tw_mock.get_write_msg(-2)
1041+
assert msg == str(path)
1042+
line = tw_mock.lines[-1]
1043+
assert line == ":3: ValueError"
10451044

10461045
@pytest.mark.parametrize(
10471046
"reproptions",

0 commit comments

Comments
 (0)