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.
This commit is contained in:
Ran Benita 2020-07-05 22:58:47 +03:00
parent 3a060b77e8
commit c15bb5d3de
6 changed files with 30 additions and 15 deletions

View File

@ -32,6 +32,7 @@ from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureManager from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit from _pytest.outcomes import exit
from _pytest.pathlib import Path from _pytest.pathlib import Path
from _pytest.pathlib import visit
from _pytest.reports import CollectReport from _pytest.reports import CollectReport
from _pytest.reports import TestReport from _pytest.reports import TestReport
from _pytest.runner import collect_one_node from _pytest.runner import collect_one_node
@ -617,9 +618,10 @@ class Session(nodes.FSCollector):
assert not names, "invalid arg {!r}".format((argpath, names)) assert not names, "invalid arg {!r}".format((argpath, names))
seen_dirs = set() # type: Set[py.path.local] seen_dirs = set() # type: Set[py.path.local]
for path in argpath.visit( for path in visit(argpath, self._recurse):
fil=self._visit_filter, rec=self._recurse, bf=True, sort=True if not path.check(file=1):
): continue
dirpath = path.dirpath() dirpath = path.dirpath()
if dirpath not in seen_dirs: if dirpath not in seen_dirs:
# Collect packages first. # Collect packages first.
@ -668,11 +670,6 @@ class Session(nodes.FSCollector):
return return
yield from m yield from m
@staticmethod
def _visit_filter(f: py.path.local) -> bool:
# TODO: Remove type: ignore once `py` is typed.
return f.check(file=1) # type: ignore
def _tryconvertpyarg(self, x: str) -> str: def _tryconvertpyarg(self, x: str) -> str:
"""Convert a dotted module name to path.""" """Convert a dotted module name to path."""
try: try:

View File

@ -16,6 +16,7 @@ from os.path import isabs
from os.path import sep from os.path import sep
from posixpath import sep as posix_sep from posixpath import sep as posix_sep
from types import ModuleType from types import ModuleType
from typing import Callable
from typing import Iterable from typing import Iterable
from typing import Iterator from typing import Iterator
from typing import Optional from typing import Optional
@ -556,3 +557,17 @@ def resolve_package_path(path: Path) -> Optional[Path]:
break break
result = parent result = parent
return result return result
def visit(
path: py.path.local, recurse: Callable[[py.path.local], bool],
) -> Iterator[py.path.local]:
"""Walk path recursively, in breadth-first order.
Entries at each directory level are sorted.
"""
entries = sorted(path.listdir())
yield from entries
for entry in entries:
if entry.check(dir=1) and recurse(entry):
yield from visit(entry, recurse)

View File

@ -65,6 +65,7 @@ from _pytest.outcomes import skip
from _pytest.pathlib import import_path from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import parts from _pytest.pathlib import parts
from _pytest.pathlib import visit
from _pytest.reports import TerminalRepr from _pytest.reports import TerminalRepr
from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning
@ -641,7 +642,7 @@ class Package(Module):
): ):
yield Module.from_parent(self, fspath=init_module) yield Module.from_parent(self, fspath=init_module)
pkg_prefixes = set() # type: Set[py.path.local] pkg_prefixes = set() # type: Set[py.path.local]
for path in this_path.visit(rec=self._recurse, bf=True, sort=True): for path in visit(this_path, recurse=self._recurse):
# We will visit our own __init__.py file, in which case we skip it. # We will visit our own __init__.py file, in which case we skip it.
is_file = path.isfile() is_file = path.isfile()
if is_file: if is_file:

View File

@ -142,14 +142,14 @@ class TestFillFixtures:
p = testdir.copy_example() p = testdir.copy_example()
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest(next(p.visit("test_*.py"))) result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py"))))
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
def test_extend_fixture_conftest_conftest(self, testdir): def test_extend_fixture_conftest_conftest(self, testdir):
p = testdir.copy_example() p = testdir.copy_example()
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest(next(p.visit("test_*.py"))) result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py"))))
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
def test_extend_fixture_conftest_plugin(self, testdir): def test_extend_fixture_conftest_plugin(self, testdir):

View File

@ -7,6 +7,7 @@ import pytest
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.main import _in_venv from _pytest.main import _in_venv
from _pytest.main import Session from _pytest.main import Session
from _pytest.pathlib import Path
from _pytest.pathlib import symlink_or_skip from _pytest.pathlib import symlink_or_skip
from _pytest.pytester import Testdir from _pytest.pytester import Testdir
@ -115,8 +116,8 @@ class TestCollectFS:
tmpdir.ensure(".whatever", "test_notfound.py") tmpdir.ensure(".whatever", "test_notfound.py")
tmpdir.ensure(".bzr", "test_notfound.py") tmpdir.ensure(".bzr", "test_notfound.py")
tmpdir.ensure("normal", "test_found.py") tmpdir.ensure("normal", "test_found.py")
for x in tmpdir.visit("test_*.py"): for x in Path(str(tmpdir)).rglob("test_*.py"):
x.write("def test_hello(): pass") x.write_text("def test_hello(): pass", "utf-8")
result = testdir.runpytest("--collect-only") result = testdir.runpytest("--collect-only")
s = result.stdout.str() s = result.stdout.str()

View File

@ -477,8 +477,9 @@ class TestConftestVisibility:
) )
) )
print("created directory structure:") print("created directory structure:")
for x in testdir.tmpdir.visit(): tmppath = Path(str(testdir.tmpdir))
print(" " + x.relto(testdir.tmpdir)) for x in tmppath.rglob(""):
print(" " + str(x.relative_to(tmppath)))
return {"runner": runner, "package": package, "swc": swc, "snc": snc} return {"runner": runner, "package": package, "swc": swc, "snc": snc}