Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More py.path -> pathlib conversions #8174

Merged
merged 10 commits into from Dec 26, 2020
6 changes: 6 additions & 0 deletions changelog/8174.trivial.rst
@@ -0,0 +1,6 @@
The following changes have been made to internal pytest types/functions:

- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``.
55 changes: 30 additions & 25 deletions src/_pytest/_code/code.py
Expand Up @@ -43,6 +43,8 @@
from _pytest._io.saferepr import saferepr
from _pytest.compat import final
from _pytest.compat import get_real_func
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath

if TYPE_CHECKING:
from typing_extensions import Literal
Expand Down Expand Up @@ -78,16 +80,16 @@ def name(self) -> str:
return self.raw.co_name

@property
def path(self) -> Union[py.path.local, str]:
def path(self) -> Union[Path, str]:
"""Return a path object pointing to source code, or an ``str`` in
case of ``OSError`` / non-existing file."""
if not self.raw.co_filename:
return ""
try:
p = py.path.local(self.raw.co_filename)
p = absolutepath(self.raw.co_filename)
# maybe don't try this checking
if not p.check():
raise OSError("py.path check failed.")
if not p.exists():
raise OSError("path check failed.")
return p
except OSError:
# XXX maybe try harder like the weird logic
Expand Down Expand Up @@ -223,7 +225,7 @@ def statement(self) -> "Source":
return source.getstatement(self.lineno)

@property
def path(self) -> Union[py.path.local, str]:
bluetech marked this conversation as resolved.
Show resolved Hide resolved
def path(self) -> Union[Path, str]:
"""Path to the source code."""
return self.frame.code.path

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

def cut(
self,
path=None,
path: Optional[Union[Path, str]] = None,
lineno: Optional[int] = None,
firstlineno: Optional[int] = None,
excludepath: Optional[py.path.local] = None,
excludepath: Optional[Path] = None,
) -> "Traceback":
"""Return a Traceback instance wrapping part of this Traceback.

Expand All @@ -353,17 +355,19 @@ def cut(
for x in self:
code = x.frame.code
codepath = code.path
if path is not None and codepath != path:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 to breaking the long conditional.

continue
if (
(path is None or codepath == path)
and (
excludepath is None
or not isinstance(codepath, py.path.local)
or not codepath.relto(excludepath)
)
and (lineno is None or x.lineno == lineno)
and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
excludepath is not None
and isinstance(codepath, Path)
and excludepath in codepath.parents
):
return Traceback(x._rawentry, self._excinfo)
continue
if lineno is not None and x.lineno != lineno:
continue
if firstlineno is not None and x.frame.code.firstlineno != firstlineno:
continue
return Traceback(x._rawentry, self._excinfo)
return self

@overload
Expand Down Expand Up @@ -801,7 +805,8 @@ def repr_traceback_entry(
message = "in %s" % (entry.name)
else:
message = excinfo and excinfo.typename or ""
path = self._makepath(entry.path)
entry_path = entry.path
path = self._makepath(entry_path)
reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
Expand All @@ -814,15 +819,15 @@ def repr_traceback_entry(
lines.extend(self.get_exconly(excinfo, indent=4))
return ReprEntry(lines, None, None, None, style)

def _makepath(self, path):
if not self.abspath:
def _makepath(self, path: Union[Path, str]) -> str:
if not self.abspath and isinstance(path, Path):
try:
np = py.path.local().bestrelpath(path)
np = bestrelpath(Path.cwd(), path)
except OSError:
return path
return str(path)
if len(np) < len(str(path)):
path = np
return path
return np
return str(path)

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


def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
"""Return source location (path, lineno) for the given object.

If the source cannot be determined return ("", -1).
Expand All @@ -1203,7 +1208,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
except TypeError:
return "", -1

fspath = fn and py.path.local(fn) or ""
fspath = fn and absolutepath(fn) or ""
lineno = -1
if fspath:
try:
Expand Down
12 changes: 7 additions & 5 deletions src/_pytest/config/__init__.py
Expand Up @@ -128,7 +128,7 @@ def filter_traceback_for_conftest_import_failure(


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


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

# plugins that were explicitly skipped with pytest.skip
# list of (module name, skip reason)
Expand Down
21 changes: 11 additions & 10 deletions src/_pytest/doctest.py
Expand Up @@ -36,6 +36,7 @@
from _pytest.fixtures import FixtureRequest
from _pytest.nodes import Collector
from _pytest.outcomes import OutcomeException
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import import_path
from _pytest.python_api import approx
from _pytest.warning_types import PytestWarning
Expand Down Expand Up @@ -120,32 +121,32 @@ def pytest_unconfigure() -> None:


def pytest_collect_file(
path: py.path.local, parent: Collector,
fspath: Path, path: py.path.local, parent: Collector,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use path anymore, we can drop it from the arguments.

Suggested change
fspath: Path, path: py.path.local, parent: Collector,
fspath: Path, parent: Collector

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually still used, just well hidden with the path/fspath naming mixing up.

) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
config = parent.config
if path.ext == ".py":
if config.option.doctestmodules and not _is_setup_py(path):
if fspath.suffix == ".py":
if config.option.doctestmodules and not _is_setup_py(fspath):
mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path)
return mod
elif _is_doctest(config, path, parent):
elif _is_doctest(config, fspath, parent):
txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path)
return txt
return None


def _is_setup_py(path: py.path.local) -> bool:
if path.basename != "setup.py":
def _is_setup_py(path: Path) -> bool:
if path.name != "setup.py":
return False
contents = path.read_binary()
contents = path.read_bytes()
return b"setuptools" in contents or b"distutils" in contents


def _is_doctest(config: Config, path: py.path.local, parent) -> bool:
if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
def _is_doctest(config: Config, path: Path, parent: Collector) -> bool:
if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path):
return True
globs = config.getoption("doctestglob") or ["test*.txt"]
for glob in globs:
if path.check(fnmatch=glob):
if fnmatch_ex(glob, path):
return True
return False

Expand Down
8 changes: 7 additions & 1 deletion src/_pytest/fixtures.py
Expand Up @@ -5,6 +5,7 @@
import warnings
from collections import defaultdict
from collections import deque
from pathlib import Path
from types import TracebackType
from typing import Any
from typing import Callable
Expand Down Expand Up @@ -58,6 +59,7 @@
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
from _pytest.store import StoreKey

if TYPE_CHECKING:
Expand Down Expand Up @@ -718,7 +720,11 @@ def _factorytraceback(self) -> List[str]:
for fixturedef in self._get_fixturestack():
factory = fixturedef.func
fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
if isinstance(fs, Path):
session: Session = self._pyfuncitem.session
p = bestrelpath(Path(session.fspath), fs)
else:
p = fs
args = _format_args(factory)
lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
return lines
Expand Down