Skip to content

Commit c15bb5d

Browse files
committed
pathlib: replace py.path.local.visit() with our own function
Part of reducing dependency on `py`. Also enables upcoming improvements. In cases where there are simpler alternatives (in tests), I used those. What's left are a couple of uses in `_pytest.main` and `_pytest.python` and they only have modest requirements, so all of the featureful code from py is not needed.
1 parent 3a060b7 commit c15bb5d

File tree

6 files changed

+30
-15
lines changed

6 files changed

+30
-15
lines changed

src/_pytest/main.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from _pytest.fixtures import FixtureManager
3333
from _pytest.outcomes import exit
3434
from _pytest.pathlib import Path
35+
from _pytest.pathlib import visit
3536
from _pytest.reports import CollectReport
3637
from _pytest.reports import TestReport
3738
from _pytest.runner import collect_one_node
@@ -617,9 +618,10 @@ def _collect(
617618
assert not names, "invalid arg {!r}".format((argpath, names))
618619

619620
seen_dirs = set() # type: Set[py.path.local]
620-
for path in argpath.visit(
621-
fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
622-
):
621+
for path in visit(argpath, self._recurse):
622+
if not path.check(file=1):
623+
continue
624+
623625
dirpath = path.dirpath()
624626
if dirpath not in seen_dirs:
625627
# Collect packages first.
@@ -668,11 +670,6 @@ def _collect(
668670
return
669671
yield from m
670672

671-
@staticmethod
672-
def _visit_filter(f: py.path.local) -> bool:
673-
# TODO: Remove type: ignore once `py` is typed.
674-
return f.check(file=1) # type: ignore
675-
676673
def _tryconvertpyarg(self, x: str) -> str:
677674
"""Convert a dotted module name to path."""
678675
try:

src/_pytest/pathlib.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from os.path import sep
1717
from posixpath import sep as posix_sep
1818
from types import ModuleType
19+
from typing import Callable
1920
from typing import Iterable
2021
from typing import Iterator
2122
from typing import Optional
@@ -556,3 +557,17 @@ def resolve_package_path(path: Path) -> Optional[Path]:
556557
break
557558
result = parent
558559
return result
560+
561+
562+
def visit(
563+
path: py.path.local, recurse: Callable[[py.path.local], bool],
564+
) -> Iterator[py.path.local]:
565+
"""Walk path recursively, in breadth-first order.
566+
567+
Entries at each directory level are sorted.
568+
"""
569+
entries = sorted(path.listdir())
570+
yield from entries
571+
for entry in entries:
572+
if entry.check(dir=1) and recurse(entry):
573+
yield from visit(entry, recurse)

src/_pytest/python.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
from _pytest.pathlib import import_path
6666
from _pytest.pathlib import ImportPathMismatchError
6767
from _pytest.pathlib import parts
68+
from _pytest.pathlib import visit
6869
from _pytest.reports import TerminalRepr
6970
from _pytest.warning_types import PytestCollectionWarning
7071
from _pytest.warning_types import PytestUnhandledCoroutineWarning
@@ -641,7 +642,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
641642
):
642643
yield Module.from_parent(self, fspath=init_module)
643644
pkg_prefixes = set() # type: Set[py.path.local]
644-
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
645+
for path in visit(this_path, recurse=self._recurse):
645646
# We will visit our own __init__.py file, in which case we skip it.
646647
is_file = path.isfile()
647648
if is_file:

testing/python/fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,14 @@ def test_extend_fixture_conftest_module(self, testdir):
142142
p = testdir.copy_example()
143143
result = testdir.runpytest()
144144
result.stdout.fnmatch_lines(["*1 passed*"])
145-
result = testdir.runpytest(next(p.visit("test_*.py")))
145+
result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py"))))
146146
result.stdout.fnmatch_lines(["*1 passed*"])
147147

148148
def test_extend_fixture_conftest_conftest(self, testdir):
149149
p = testdir.copy_example()
150150
result = testdir.runpytest()
151151
result.stdout.fnmatch_lines(["*1 passed*"])
152-
result = testdir.runpytest(next(p.visit("test_*.py")))
152+
result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py"))))
153153
result.stdout.fnmatch_lines(["*1 passed*"])
154154

155155
def test_extend_fixture_conftest_plugin(self, testdir):

testing/test_collection.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from _pytest.config import ExitCode
88
from _pytest.main import _in_venv
99
from _pytest.main import Session
10+
from _pytest.pathlib import Path
1011
from _pytest.pathlib import symlink_or_skip
1112
from _pytest.pytester import Testdir
1213

@@ -115,8 +116,8 @@ def test_ignored_certain_directories(self, testdir):
115116
tmpdir.ensure(".whatever", "test_notfound.py")
116117
tmpdir.ensure(".bzr", "test_notfound.py")
117118
tmpdir.ensure("normal", "test_found.py")
118-
for x in tmpdir.visit("test_*.py"):
119-
x.write("def test_hello(): pass")
119+
for x in Path(str(tmpdir)).rglob("test_*.py"):
120+
x.write_text("def test_hello(): pass", "utf-8")
120121

121122
result = testdir.runpytest("--collect-only")
122123
s = result.stdout.str()

testing/test_conftest.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,9 @@ def test_no_conftest(fxtr):
477477
)
478478
)
479479
print("created directory structure:")
480-
for x in testdir.tmpdir.visit():
481-
print(" " + x.relto(testdir.tmpdir))
480+
tmppath = Path(str(testdir.tmpdir))
481+
for x in tmppath.rglob(""):
482+
print(" " + str(x.relative_to(tmppath)))
482483

483484
return {"runner": runner, "package": package, "swc": swc, "snc": snc}
484485

0 commit comments

Comments
 (0)