Skip to content

Commit d8d2df7

Browse files
authored
Merge pull request #8174 from bluetech/py-to-pathlib-5
More py.path -> pathlib conversions
2 parents da01ee0 + ca4effc commit d8d2df7

19 files changed

+336
-294
lines changed

changelog/8174.trivial.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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``.
6+
- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``.

src/_pytest/_code/code.py

Lines changed: 30 additions & 25 deletions
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/config/__init__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def filter_traceback_for_conftest_import_failure(
128128

129129

130130
def main(
131-
args: Optional[Union[List[str], py.path.local]] = None,
131+
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
132132
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
133133
) -> Union[int, ExitCode]:
134134
"""Perform an in-process test run.
@@ -295,13 +295,15 @@ def get_plugin_manager() -> "PytestPluginManager":
295295

296296

297297
def _prepareconfig(
298-
args: Optional[Union[py.path.local, List[str]]] = None,
298+
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
299299
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
300300
) -> "Config":
301301
if args is None:
302302
args = sys.argv[1:]
303-
elif isinstance(args, py.path.local):
304-
args = [str(args)]
303+
# TODO: Remove type-ignore after next mypy release.
304+
# https://github.com/python/typeshed/commit/076983eec45e739c68551cb6119fd7d85fd4afa9
305+
elif isinstance(args, os.PathLike): # type: ignore[misc]
306+
args = [os.fspath(args)]
305307
elif not isinstance(args, list):
306308
msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
307309
raise TypeError(msg.format(args, type(args)))
@@ -346,7 +348,7 @@ def __init__(self) -> None:
346348
self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
347349
self._confcutdir: Optional[Path] = None
348350
self._noconftest = False
349-
self._duplicatepaths: Set[py.path.local] = set()
351+
self._duplicatepaths: Set[Path] = set()
350352

351353
# plugins that were explicitly skipped with pytest.skip
352354
# list of (module name, skip reason)

src/_pytest/doctest.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from _pytest.fixtures import FixtureRequest
3737
from _pytest.nodes import Collector
3838
from _pytest.outcomes import OutcomeException
39+
from _pytest.pathlib import fnmatch_ex
3940
from _pytest.pathlib import import_path
4041
from _pytest.python_api import approx
4142
from _pytest.warning_types import PytestWarning
@@ -120,32 +121,32 @@ def pytest_unconfigure() -> None:
120121

121122

122123
def pytest_collect_file(
123-
path: py.path.local, parent: Collector,
124+
fspath: Path, path: py.path.local, parent: Collector,
124125
) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
125126
config = parent.config
126-
if path.ext == ".py":
127-
if config.option.doctestmodules and not _is_setup_py(path):
127+
if fspath.suffix == ".py":
128+
if config.option.doctestmodules and not _is_setup_py(fspath):
128129
mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path)
129130
return mod
130-
elif _is_doctest(config, path, parent):
131+
elif _is_doctest(config, fspath, parent):
131132
txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path)
132133
return txt
133134
return None
134135

135136

136-
def _is_setup_py(path: py.path.local) -> bool:
137-
if path.basename != "setup.py":
137+
def _is_setup_py(path: Path) -> bool:
138+
if path.name != "setup.py":
138139
return False
139-
contents = path.read_binary()
140+
contents = path.read_bytes()
140141
return b"setuptools" in contents or b"distutils" in contents
141142

142143

143-
def _is_doctest(config: Config, path: py.path.local, parent) -> bool:
144-
if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
144+
def _is_doctest(config: Config, path: Path, parent: Collector) -> bool:
145+
if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path):
145146
return True
146147
globs = config.getoption("doctestglob") or ["test*.txt"]
147148
for glob in globs:
148-
if path.check(fnmatch=glob):
149+
if fnmatch_ex(glob, path):
149150
return True
150151
return False
151152

src/_pytest/fixtures.py

Lines changed: 7 additions & 1 deletion
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

0 commit comments

Comments
 (0)