Skip to content

Commit 37b154b

Browse files
authored
Merge pull request #8122 from bluetech/py-to-pathlib-3
Some py.path.local -> pathlib.Path
2 parents 0958204 + ed658d6 commit 37b154b

File tree

10 files changed

+141
-126
lines changed

10 files changed

+141
-126
lines changed

src/_pytest/config/__init__.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@
5050
from _pytest.compat import importlib_metadata
5151
from _pytest.outcomes import fail
5252
from _pytest.outcomes import Skipped
53+
from _pytest.pathlib import absolutepath
5354
from _pytest.pathlib import bestrelpath
5455
from _pytest.pathlib import import_path
5556
from _pytest.pathlib import ImportMode
57+
from _pytest.pathlib import resolve_package_path
5658
from _pytest.store import Store
5759
from _pytest.warning_types import PytestConfigWarning
5860

@@ -102,9 +104,7 @@ class ExitCode(enum.IntEnum):
102104

103105
class ConftestImportFailure(Exception):
104106
def __init__(
105-
self,
106-
path: py.path.local,
107-
excinfo: Tuple[Type[Exception], Exception, TracebackType],
107+
self, path: Path, excinfo: Tuple[Type[Exception], Exception, TracebackType],
108108
) -> None:
109109
super().__init__(path, excinfo)
110110
self.path = path
@@ -342,9 +342,9 @@ def __init__(self) -> None:
342342
self._conftest_plugins: Set[types.ModuleType] = set()
343343

344344
# State related to local conftest plugins.
345-
self._dirpath2confmods: Dict[py.path.local, List[types.ModuleType]] = {}
345+
self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
346346
self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
347-
self._confcutdir: Optional[py.path.local] = None
347+
self._confcutdir: Optional[Path] = None
348348
self._noconftest = False
349349
self._duplicatepaths: Set[py.path.local] = set()
350350

@@ -479,9 +479,9 @@ def _set_initial_conftests(self, namespace: argparse.Namespace) -> None:
479479
All builtin and 3rd party plugins will have been loaded, however, so
480480
common options will not confuse our logic here.
481481
"""
482-
current = py.path.local()
482+
current = Path.cwd()
483483
self._confcutdir = (
484-
current.join(namespace.confcutdir, abs=True)
484+
absolutepath(current / namespace.confcutdir)
485485
if namespace.confcutdir
486486
else None
487487
)
@@ -495,51 +495,51 @@ def _set_initial_conftests(self, namespace: argparse.Namespace) -> None:
495495
i = path.find("::")
496496
if i != -1:
497497
path = path[:i]
498-
anchor = current.join(path, abs=1)
498+
anchor = absolutepath(current / path)
499499
if anchor.exists(): # we found some file object
500500
self._try_load_conftest(anchor, namespace.importmode)
501501
foundanchor = True
502502
if not foundanchor:
503503
self._try_load_conftest(current, namespace.importmode)
504504

505505
def _try_load_conftest(
506-
self, anchor: py.path.local, importmode: Union[str, ImportMode]
506+
self, anchor: Path, importmode: Union[str, ImportMode]
507507
) -> None:
508508
self._getconftestmodules(anchor, importmode)
509509
# let's also consider test* subdirs
510-
if anchor.check(dir=1):
511-
for x in anchor.listdir("test*"):
512-
if x.check(dir=1):
510+
if anchor.is_dir():
511+
for x in anchor.glob("test*"):
512+
if x.is_dir():
513513
self._getconftestmodules(x, importmode)
514514

515515
@lru_cache(maxsize=128)
516516
def _getconftestmodules(
517-
self, path: py.path.local, importmode: Union[str, ImportMode],
517+
self, path: Path, importmode: Union[str, ImportMode],
518518
) -> List[types.ModuleType]:
519519
if self._noconftest:
520520
return []
521521

522-
if path.isfile():
523-
directory = path.dirpath()
522+
if path.is_file():
523+
directory = path.parent
524524
else:
525525
directory = path
526526

527527
# XXX these days we may rather want to use config.rootpath
528528
# and allow users to opt into looking into the rootdir parent
529529
# directories instead of requiring to specify confcutdir.
530530
clist = []
531-
for parent in directory.parts():
532-
if self._confcutdir and self._confcutdir.relto(parent):
531+
for parent in reversed((directory, *directory.parents)):
532+
if self._confcutdir and parent in self._confcutdir.parents:
533533
continue
534-
conftestpath = parent.join("conftest.py")
535-
if conftestpath.isfile():
534+
conftestpath = parent / "conftest.py"
535+
if conftestpath.is_file():
536536
mod = self._importconftest(conftestpath, importmode)
537537
clist.append(mod)
538538
self._dirpath2confmods[directory] = clist
539539
return clist
540540

541541
def _rget_with_confmod(
542-
self, name: str, path: py.path.local, importmode: Union[str, ImportMode],
542+
self, name: str, path: Path, importmode: Union[str, ImportMode],
543543
) -> Tuple[types.ModuleType, Any]:
544544
modules = self._getconftestmodules(path, importmode)
545545
for mod in reversed(modules):
@@ -550,21 +550,21 @@ def _rget_with_confmod(
550550
raise KeyError(name)
551551

552552
def _importconftest(
553-
self, conftestpath: py.path.local, importmode: Union[str, ImportMode],
553+
self, conftestpath: Path, importmode: Union[str, ImportMode],
554554
) -> types.ModuleType:
555555
# Use a resolved Path object as key to avoid loading the same conftest
556556
# twice with build systems that create build directories containing
557557
# symlinks to actual files.
558558
# Using Path().resolve() is better than py.path.realpath because
559559
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
560-
key = Path(str(conftestpath)).resolve()
560+
key = conftestpath.resolve()
561561

562562
with contextlib.suppress(KeyError):
563563
return self._conftestpath2mod[key]
564564

565-
pkgpath = conftestpath.pypkgpath()
565+
pkgpath = resolve_package_path(conftestpath)
566566
if pkgpath is None:
567-
_ensure_removed_sysmodule(conftestpath.purebasename)
567+
_ensure_removed_sysmodule(conftestpath.stem)
568568

569569
try:
570570
mod = import_path(conftestpath, mode=importmode)
@@ -577,18 +577,18 @@ def _importconftest(
577577

578578
self._conftest_plugins.add(mod)
579579
self._conftestpath2mod[key] = mod
580-
dirpath = conftestpath.dirpath()
580+
dirpath = conftestpath.parent
581581
if dirpath in self._dirpath2confmods:
582582
for path, mods in self._dirpath2confmods.items():
583-
if path and path.relto(dirpath) or path == dirpath:
583+
if path and dirpath in path.parents or path == dirpath:
584584
assert mod not in mods
585585
mods.append(mod)
586586
self.trace(f"loading conftestmodule {mod!r}")
587587
self.consider_conftest(mod)
588588
return mod
589589

590590
def _check_non_top_pytest_plugins(
591-
self, mod: types.ModuleType, conftestpath: py.path.local,
591+
self, mod: types.ModuleType, conftestpath: Path,
592592
) -> None:
593593
if (
594594
hasattr(mod, "pytest_plugins")
@@ -1412,21 +1412,23 @@ def _getini(self, name: str):
14121412
assert type in [None, "string"]
14131413
return value
14141414

1415-
def _getconftest_pathlist(
1416-
self, name: str, path: py.path.local
1417-
) -> Optional[List[py.path.local]]:
1415+
def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
14181416
try:
14191417
mod, relroots = self.pluginmanager._rget_with_confmod(
14201418
name, path, self.getoption("importmode")
14211419
)
14221420
except KeyError:
14231421
return None
1424-
modpath = py.path.local(mod.__file__).dirpath()
1425-
values: List[py.path.local] = []
1422+
modpath = Path(mod.__file__).parent
1423+
values: List[Path] = []
14261424
for relroot in relroots:
1427-
if not isinstance(relroot, py.path.local):
1425+
if isinstance(relroot, Path):
1426+
pass
1427+
elif isinstance(relroot, py.path.local):
1428+
relroot = Path(relroot)
1429+
else:
14281430
relroot = relroot.replace("/", os.sep)
1429-
relroot = modpath.join(relroot, abs=True)
1431+
relroot = absolutepath(modpath / relroot)
14301432
values.append(relroot)
14311433
return values
14321434

src/_pytest/doctest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import types
88
import warnings
99
from contextlib import contextmanager
10+
from pathlib import Path
1011
from typing import Any
1112
from typing import Callable
1213
from typing import Dict
@@ -525,7 +526,7 @@ def _find(
525526

526527
if self.fspath.basename == "conftest.py":
527528
module = self.config.pluginmanager._importconftest(
528-
self.fspath, self.config.getoption("importmode")
529+
Path(self.fspath), self.config.getoption("importmode")
529530
)
530531
else:
531532
try:

src/_pytest/main.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -371,22 +371,23 @@ def _in_venv(path: py.path.local) -> bool:
371371

372372

373373
def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]:
374-
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
374+
path_ = Path(path)
375+
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path_.parent)
375376
ignore_paths = ignore_paths or []
376377
excludeopt = config.getoption("ignore")
377378
if excludeopt:
378-
ignore_paths.extend([py.path.local(x) for x in excludeopt])
379+
ignore_paths.extend(absolutepath(x) for x in excludeopt)
379380

380-
if py.path.local(path) in ignore_paths:
381+
if path_ in ignore_paths:
381382
return True
382383

383384
ignore_globs = config._getconftest_pathlist(
384-
"collect_ignore_glob", path=path.dirpath()
385+
"collect_ignore_glob", path=path_.parent
385386
)
386387
ignore_globs = ignore_globs or []
387388
excludeglobopt = config.getoption("ignore_glob")
388389
if excludeglobopt:
389-
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
390+
ignore_globs.extend(absolutepath(x) for x in excludeglobopt)
390391

391392
if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs):
392393
return True
@@ -512,12 +513,12 @@ def pytest_runtest_logreport(
512513
def isinitpath(self, path: py.path.local) -> bool:
513514
return path in self._initialpaths
514515

515-
def gethookproxy(self, fspath: py.path.local):
516+
def gethookproxy(self, fspath: "os.PathLike[str]"):
516517
# Check if we have the common case of running
517518
# hooks with all conftest.py files.
518519
pm = self.config.pluginmanager
519520
my_conftestmodules = pm._getconftestmodules(
520-
fspath, self.config.getoption("importmode")
521+
Path(fspath), self.config.getoption("importmode")
521522
)
522523
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
523524
if remove_mods:
@@ -668,8 +669,9 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
668669
# No point in finding packages when collecting doctests.
669670
if not self.config.getoption("doctestmodules", False):
670671
pm = self.config.pluginmanager
672+
confcutdir = py.path.local(pm._confcutdir) if pm._confcutdir else None
671673
for parent in reversed(argpath.parts()):
672-
if pm._confcutdir and pm._confcutdir.relto(parent):
674+
if confcutdir and confcutdir.relto(parent):
673675
break
674676

675677
if parent.isdir():

src/_pytest/nodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ def from_parent(cls, parent, *, fspath, **kw):
520520
"""The public constructor."""
521521
return super().from_parent(parent=parent, fspath=fspath, **kw)
522522

523-
def gethookproxy(self, fspath: py.path.local):
523+
def gethookproxy(self, fspath: "os.PathLike[str]"):
524524
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
525525
return self.session.gethookproxy(fspath)
526526

src/_pytest/python.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ def setup(self) -> None:
653653
func = partial(_call_with_optional_argument, teardown_module, self.obj)
654654
self.addfinalizer(func)
655655

656-
def gethookproxy(self, fspath: py.path.local):
656+
def gethookproxy(self, fspath: "os.PathLike[str]"):
657657
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
658658
return self.session.gethookproxy(fspath)
659659

testing/python/fixtures.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from _pytest.config import ExitCode
99
from _pytest.fixtures import FixtureRequest
1010
from _pytest.pytester import get_public_names
11+
from _pytest.pytester import Pytester
1112
from _pytest.pytester import Testdir
1213

1314

@@ -1961,8 +1962,10 @@ def test_result(arg):
19611962
reprec = testdir.inline_run("-v", "-s")
19621963
reprec.assertoutcome(passed=4)
19631964

1964-
def test_class_function_parametrization_finalization(self, testdir):
1965-
p = testdir.makeconftest(
1965+
def test_class_function_parametrization_finalization(
1966+
self, pytester: Pytester
1967+
) -> None:
1968+
p = pytester.makeconftest(
19661969
"""
19671970
import pytest
19681971
import pprint
@@ -1984,7 +1987,7 @@ def fin():
19841987
request.addfinalizer(fin)
19851988
"""
19861989
)
1987-
testdir.makepyfile(
1990+
pytester.makepyfile(
19881991
"""
19891992
import pytest
19901993
@@ -1996,8 +1999,7 @@ def test_2(self):
19961999
pass
19972000
"""
19982001
)
1999-
confcut = f"--confcutdir={testdir.tmpdir}"
2000-
reprec = testdir.inline_run("-v", "-s", confcut)
2002+
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
20012003
reprec.assertoutcome(passed=8)
20022004
config = reprec.getcalls("pytest_unconfigure")[0].config
20032005
values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[

testing/test_collection.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,9 @@ def pytest_ignore_collect(path, config):
364364
def test_collectignore_exclude_on_option(self, pytester: Pytester) -> None:
365365
pytester.makeconftest(
366366
"""
367-
collect_ignore = ['hello', 'test_world.py']
367+
import py
368+
from pathlib import Path
369+
collect_ignore = [py.path.local('hello'), 'test_world.py', Path('bye')]
368370
def pytest_addoption(parser):
369371
parser.addoption("--XX", action="store_true", default=False)
370372
def pytest_configure(config):

testing/test_config.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -574,16 +574,16 @@ def test_getoption(self, pytester: Pytester) -> None:
574574
config.getvalue("x")
575575
assert config.getoption("x", 1) == 1
576576

577-
def test_getconftest_pathlist(self, pytester: Pytester, tmpdir) -> None:
578-
somepath = tmpdir.join("x", "y", "z")
579-
p = tmpdir.join("conftest.py")
580-
p.write("pathlist = ['.', %r]" % str(somepath))
577+
def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None:
578+
somepath = tmp_path.joinpath("x", "y", "z")
579+
p = tmp_path.joinpath("conftest.py")
580+
p.write_text(f"pathlist = ['.', {str(somepath)!r}]")
581581
config = pytester.parseconfigure(p)
582-
assert config._getconftest_pathlist("notexist", path=tmpdir) is None
583-
pl = config._getconftest_pathlist("pathlist", path=tmpdir) or []
582+
assert config._getconftest_pathlist("notexist", path=tmp_path) is None
583+
pl = config._getconftest_pathlist("pathlist", path=tmp_path) or []
584584
print(pl)
585585
assert len(pl) == 2
586-
assert pl[0] == tmpdir
586+
assert pl[0] == tmp_path
587587
assert pl[1] == somepath
588588

589589
@pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
@@ -1935,10 +1935,10 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives
19351935
assert msg not in res.stdout.str()
19361936

19371937

1938-
def test_conftest_import_error_repr(tmpdir: py.path.local) -> None:
1938+
def test_conftest_import_error_repr(tmp_path: Path) -> None:
19391939
"""`ConftestImportFailure` should use a short error message and readable
19401940
path to the failed conftest.py file."""
1941-
path = tmpdir.join("foo/conftest.py")
1941+
path = tmp_path.joinpath("foo/conftest.py")
19421942
with pytest.raises(
19431943
ConftestImportFailure,
19441944
match=re.escape(f"RuntimeError: some error (from {path})"),

0 commit comments

Comments
 (0)