Skip to content

Commit

Permalink
[wip] implement Node.path as pathlib.Path
Browse files Browse the repository at this point in the history
there is a bug in module collection left that i dont see tonight, lets retry tommorow
  • Loading branch information
RonnyPfannschmidt committed Feb 7, 2021
1 parent 325d701 commit 436932e
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 76 deletions.
10 changes: 5 additions & 5 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,12 @@ def pytest_make_collect_report(self, collector: nodes.Collector):
lf_paths = self.lfplugin._last_failed_paths
res.result = sorted(
res.result,
key=lambda x: 0 if Path(str(x.fspath)) in lf_paths else 1,
key=lambda x: x.path not in lf_paths,
)
return

elif isinstance(collector, Module):
if Path(str(collector.fspath)) in self.lfplugin._last_failed_paths:
if collector.path in self.lfplugin._last_failed_paths:
out = yield
res = out.get_result()
result = res.result
Expand All @@ -246,7 +246,7 @@ def pytest_make_collect_report(self, collector: nodes.Collector):
for x in result
if x.nodeid in lastfailed
# Include any passed arguments (not trivial to filter).
or session.isinitpath(x.fspath)
or session.isinitpath(x.path)
# Keep all sub-collectors.
or isinstance(x, nodes.Collector)
]
Expand All @@ -266,7 +266,7 @@ def pytest_make_collect_report(
# test-bearing paths and doesn't try to include the paths of their
# packages, so don't filter them.
if isinstance(collector, Module) and not isinstance(collector, Package):
if Path(str(collector.fspath)) not in self.lfplugin._last_failed_paths:
if collector.path not in self.lfplugin._last_failed_paths:
self.lfplugin._skipped_files += 1

return CollectReport(
Expand Down Expand Up @@ -415,7 +415,7 @@ def pytest_collection_modifyitems(
self.cached_nodeids.update(item.nodeid for item in items)

def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)
return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return]

def pytest_sessionfinish(self) -> None:
config = self.config
Expand Down
12 changes: 12 additions & 0 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import enum
import functools
import inspect
import os
import re
import sys
from contextlib import contextmanager
Expand All @@ -18,6 +19,7 @@
from typing import Union

import attr
import py

from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
Expand All @@ -30,6 +32,16 @@
_T = TypeVar("_T")
_S = TypeVar("_S")

#: constant to prepare valuing py.path.local replacements/lazy proxies later on
# intended for removal in pytest 8.0 or 9.0

LEGACY_PATH = py.path.local


def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
"""Internal wrapper to prepare lazy proxies for py.path.local instances"""
return py.path.local(path)


# fmt: off
# Singleton type for NOTSET, as described in:
Expand Down
8 changes: 8 additions & 0 deletions src/_pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
)


NODE_FSPATH = UnformattedWarning(
PytestDeprecationWarning,
"{type}.fspath is deprecated and will be replaced by {type}.path.\n"
"see TODO;URL for details on replacing py.path.local with pathlib.Path",
)

# You want to make some `__init__` or function "private".
#
# def my_private_function(some, args):
Expand All @@ -106,6 +112,8 @@
#
# All other calls will get the default _ispytest=False and trigger
# the warning (possibly error in the future).


def check_ispytest(ispytest: bool) -> None:
if not ispytest:
warn(PRIVATE, stacklevel=3)
21 changes: 11 additions & 10 deletions src/_pytest/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest.compat import legacy_path
from _pytest.compat import safe_getattr
from _pytest.config import Config
from _pytest.config.argparsing import Parser
Expand Down Expand Up @@ -128,10 +129,10 @@ def pytest_collect_file(
config = parent.config
if fspath.suffix == ".py":
if config.option.doctestmodules and not _is_setup_py(fspath):
mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path)
mod: DoctestModule = DoctestModule.from_parent(parent, path=fspath)
return mod
elif _is_doctest(config, fspath, parent):
txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path)
txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=fspath)
return txt
return None

Expand Down Expand Up @@ -378,7 +379,7 @@ def repr_failure( # type: ignore[override]

def reportinfo(self):
assert self.dtest is not None
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
return legacy_path(self.path), self.dtest.lineno, "[doctest] %s" % self.name


def _get_flag_lookup() -> Dict[str, int]:
Expand Down Expand Up @@ -425,9 +426,9 @@ def collect(self) -> Iterable[DoctestItem]:
# Inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker.
encoding = self.config.getini("doctest_encoding")
text = self.fspath.read_text(encoding)
filename = str(self.fspath)
name = self.fspath.basename
text = self.path.read_text(encoding)
filename = str(self.path)
name = self.path.name
globs = {"__name__": "__main__"}

optionflags = get_optionflags(self)
Expand Down Expand Up @@ -534,16 +535,16 @@ def _find(
self, tests, obj, name, module, source_lines, globs, seen
)

if self.fspath.basename == "conftest.py":
if self.path.name == "conftest.py":
module = self.config.pluginmanager._importconftest(
Path(self.fspath), self.config.getoption("importmode")
self.path, self.config.getoption("importmode")
)
else:
try:
module = import_path(self.fspath)
module = import_path(self.path)
except ImportError:
if self.config.getvalue("doctest_ignore_import_errors"):
pytest.skip("unable to import module %r" % self.fspath)
pytest.skip("unable to import module %r" % self.path)
else:
raise
# Uses internal doctest module parsing mechanism.
Expand Down
25 changes: 16 additions & 9 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from typing import Union

import attr
import py

import _pytest
from _pytest import nodes
Expand All @@ -46,13 +45,16 @@
from _pytest.compat import getimfunc
from _pytest.compat import getlocation
from _pytest.compat import is_generator
from _pytest.compat import LEGACY_PATH
from _pytest.compat import legacy_path
from _pytest.compat import NOTSET
from _pytest.compat import safe_getattr
from _pytest.config import _PluggyPlugin
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import FILLFUNCARGS
from _pytest.deprecated import NODE_FSPATH
from _pytest.deprecated import YIELD_FIXTURE
from _pytest.mark import Mark
from _pytest.mark import ParameterSet
Expand Down Expand Up @@ -256,12 +258,12 @@ def get_parametrized_fixture_keys(item: nodes.Item, scopenum: int) -> Iterator[_
if scopenum == 0: # session
key: _Key = (argname, param_index)
elif scopenum == 1: # package
key = (argname, param_index, item.fspath.dirpath())
key = (argname, param_index, item.path.parent)
elif scopenum == 2: # module
key = (argname, param_index, item.fspath)
key = (argname, param_index, item.path)
elif scopenum == 3: # class
item_cls = item.cls # type: ignore[attr-defined]
key = (argname, param_index, item.fspath, item_cls)
key = (argname, param_index, item.path, item_cls)
yield key


Expand Down Expand Up @@ -519,12 +521,17 @@ def module(self):
return self._pyfuncitem.getparent(_pytest.python.Module).obj

@property
def fspath(self) -> py.path.local:
"""The file system path of the test module which collected this test."""
def fspath(self) -> LEGACY_PATH:
"""(deprecated) The file system path of the test module which collected this test."""
warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2)
return legacy_path(self.path)

@property
def path(self) -> Path:
if self.scope not in ("function", "class", "module", "package"):
raise AttributeError(f"module not available in {self.scope}-scoped context")
# TODO: Remove ignore once _pyfuncitem is properly typed.
return self._pyfuncitem.fspath # type: ignore
return self._pyfuncitem.path # type: ignore

@property
def keywords(self) -> MutableMapping[str, Any]:
Expand Down Expand Up @@ -1040,7 +1047,7 @@ def finish(self, request: SubRequest) -> None:
if exc:
raise exc
finally:
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
hook = self._fixturemanager.session.gethookproxy(request.node.path)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
# Even if finalization fails, we invalidate the cached fixture
# value and remove all finalizers because they may be bound methods
Expand Down Expand Up @@ -1075,7 +1082,7 @@ def execute(self, request: SubRequest) -> _FixtureValue:
self.finish(request)
assert self.cached_result is None

hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
hook = self._fixturemanager.session.gethookproxy(request.node.path)
result = hook.pytest_fixture_setup(fixturedef=self, request=request)
return result

Expand Down
11 changes: 8 additions & 3 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,12 @@ class Session(nodes.FSCollector):

def __init__(self, config: Config) -> None:
super().__init__(
config.rootdir, parent=None, config=config, session=self, nodeid=""
path=config.rootpath,
fspath=config.rootdir,
parent=None,
config=config,
session=self,
nodeid="",
)
self.testsfailed = 0
self.testscollected = 0
Expand Down Expand Up @@ -688,7 +693,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
if col:
if isinstance(col[0], Package):
pkg_roots[str(parent)] = col[0]
node_cache1[Path(col[0].fspath)] = [col[0]]
node_cache1[col[0].path] = [col[0]]

# If it's a directory argument, recurse and look for any Subpackages.
# Let the Package collector deal with subnodes, don't collect here.
Expand Down Expand Up @@ -717,7 +722,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
continue

for x in self._collectfile(path):
key2 = (type(x), Path(x.fspath))
key2 = (type(x), x.path)
if key2 in node_cache2:
yield node_cache2[key2]
else:
Expand Down

0 comments on commit 436932e

Please sign in to comment.