[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2024-04-13 00:00:18 +00:00
parent 72ad6dc953
commit f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions

View file

@ -1,14 +1,15 @@
"""Core implementation of the testing process: init, session, runtest loop."""
from __future__ import annotations
import argparse
import dataclasses
import fnmatch
import functools
import importlib
import importlib.util
import os
from pathlib import Path
import sys
import warnings
from pathlib import Path
from typing import AbstractSet
from typing import Callable
from typing import Dict
@ -24,12 +25,10 @@ from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
import warnings
import pluggy
from _pytest import nodes
import _pytest._code
import pluggy
from _pytest import nodes
from _pytest.config import Config
from _pytest.config import directory_arg
from _pytest.config import ExitCode
@ -58,195 +57,195 @@ if TYPE_CHECKING:
def pytest_addoption(parser: Parser) -> None:
parser.addini(
"norecursedirs",
"Directory patterns to avoid for recursion",
type="args",
'norecursedirs',
'Directory patterns to avoid for recursion',
type='args',
default=[
"*.egg",
".*",
"_darcs",
"build",
"CVS",
"dist",
"node_modules",
"venv",
"{arch}",
'*.egg',
'.*',
'_darcs',
'build',
'CVS',
'dist',
'node_modules',
'venv',
'{arch}',
],
)
parser.addini(
"testpaths",
"Directories to search for tests when no files or directories are given on the "
"command line",
type="args",
'testpaths',
'Directories to search for tests when no files or directories are given on the '
'command line',
type='args',
default=[],
)
group = parser.getgroup("general", "Running and selection options")
group = parser.getgroup('general', 'Running and selection options')
group._addoption(
"-x",
"--exitfirst",
action="store_const",
dest="maxfail",
'-x',
'--exitfirst',
action='store_const',
dest='maxfail',
const=1,
help="Exit instantly on first error or failed test",
help='Exit instantly on first error or failed test',
)
group = parser.getgroup("pytest-warnings")
group = parser.getgroup('pytest-warnings')
group.addoption(
"-W",
"--pythonwarnings",
action="append",
help="Set which warnings to report, see -W option of Python itself",
'-W',
'--pythonwarnings',
action='append',
help='Set which warnings to report, see -W option of Python itself',
)
parser.addini(
"filterwarnings",
type="linelist",
help="Each line specifies a pattern for "
"warnings.filterwarnings. "
"Processed after -W/--pythonwarnings.",
'filterwarnings',
type='linelist',
help='Each line specifies a pattern for '
'warnings.filterwarnings. '
'Processed after -W/--pythonwarnings.',
)
group._addoption(
"--maxfail",
metavar="num",
action="store",
'--maxfail',
metavar='num',
action='store',
type=int,
dest="maxfail",
dest='maxfail',
default=0,
help="Exit after first num failures or errors",
help='Exit after first num failures or errors',
)
group._addoption(
"--strict-config",
action="store_true",
help="Any warnings encountered while parsing the `pytest` section of the "
"configuration file raise errors",
'--strict-config',
action='store_true',
help='Any warnings encountered while parsing the `pytest` section of the '
'configuration file raise errors',
)
group._addoption(
"--strict-markers",
action="store_true",
help="Markers not registered in the `markers` section of the configuration "
"file raise errors",
'--strict-markers',
action='store_true',
help='Markers not registered in the `markers` section of the configuration '
'file raise errors',
)
group._addoption(
"--strict",
action="store_true",
help="(Deprecated) alias to --strict-markers",
'--strict',
action='store_true',
help='(Deprecated) alias to --strict-markers',
)
group._addoption(
"-c",
"--config-file",
metavar="FILE",
'-c',
'--config-file',
metavar='FILE',
type=str,
dest="inifilename",
help="Load configuration from `FILE` instead of trying to locate one of the "
"implicit configuration files.",
dest='inifilename',
help='Load configuration from `FILE` instead of trying to locate one of the '
'implicit configuration files.',
)
group._addoption(
"--continue-on-collection-errors",
action="store_true",
'--continue-on-collection-errors',
action='store_true',
default=False,
dest="continue_on_collection_errors",
help="Force test execution even if collection errors occur",
dest='continue_on_collection_errors',
help='Force test execution even if collection errors occur',
)
group._addoption(
"--rootdir",
action="store",
dest="rootdir",
'--rootdir',
action='store',
dest='rootdir',
help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
"'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
"'$HOME/root_dir'.",
)
group = parser.getgroup("collect", "collection")
group = parser.getgroup('collect', 'collection')
group.addoption(
"--collectonly",
"--collect-only",
"--co",
action="store_true",
'--collectonly',
'--collect-only',
'--co',
action='store_true',
help="Only collect tests, don't execute them",
)
group.addoption(
"--pyargs",
action="store_true",
help="Try to interpret all arguments as Python packages",
'--pyargs',
action='store_true',
help='Try to interpret all arguments as Python packages',
)
group.addoption(
"--ignore",
action="append",
metavar="path",
help="Ignore path during collection (multi-allowed)",
'--ignore',
action='append',
metavar='path',
help='Ignore path during collection (multi-allowed)',
)
group.addoption(
"--ignore-glob",
action="append",
metavar="path",
help="Ignore path pattern during collection (multi-allowed)",
'--ignore-glob',
action='append',
metavar='path',
help='Ignore path pattern during collection (multi-allowed)',
)
group.addoption(
"--deselect",
action="append",
metavar="nodeid_prefix",
help="Deselect item (via node id prefix) during collection (multi-allowed)",
'--deselect',
action='append',
metavar='nodeid_prefix',
help='Deselect item (via node id prefix) during collection (multi-allowed)',
)
group.addoption(
"--confcutdir",
dest="confcutdir",
'--confcutdir',
dest='confcutdir',
default=None,
metavar="dir",
type=functools.partial(directory_arg, optname="--confcutdir"),
metavar='dir',
type=functools.partial(directory_arg, optname='--confcutdir'),
help="Only load conftest.py's relative to specified dir",
)
group.addoption(
"--noconftest",
action="store_true",
dest="noconftest",
'--noconftest',
action='store_true',
dest='noconftest',
default=False,
help="Don't load any conftest.py files",
)
group.addoption(
"--keepduplicates",
"--keep-duplicates",
action="store_true",
dest="keepduplicates",
'--keepduplicates',
'--keep-duplicates',
action='store_true',
dest='keepduplicates',
default=False,
help="Keep duplicate tests",
help='Keep duplicate tests',
)
group.addoption(
"--collect-in-virtualenv",
action="store_true",
dest="collect_in_virtualenv",
'--collect-in-virtualenv',
action='store_true',
dest='collect_in_virtualenv',
default=False,
help="Don't ignore tests in a local virtualenv directory",
)
group.addoption(
"--import-mode",
default="prepend",
choices=["prepend", "append", "importlib"],
dest="importmode",
help="Prepend/append to sys.path when importing test modules and conftest "
"files. Default: prepend.",
'--import-mode',
default='prepend',
choices=['prepend', 'append', 'importlib'],
dest='importmode',
help='Prepend/append to sys.path when importing test modules and conftest '
'files. Default: prepend.',
)
parser.addini(
"consider_namespace_packages",
type="bool",
'consider_namespace_packages',
type='bool',
default=False,
help="Consider namespace packages when resolving module names during import",
help='Consider namespace packages when resolving module names during import',
)
group = parser.getgroup("debugconfig", "test session debugging and configuration")
group = parser.getgroup('debugconfig', 'test session debugging and configuration')
group.addoption(
"--basetemp",
dest="basetemp",
'--basetemp',
dest='basetemp',
default=None,
type=validate_basetemp,
metavar="dir",
metavar='dir',
help=(
"Base temporary directory for this test run. "
"(Warning: this directory is removed if it exists.)"
'Base temporary directory for this test run. '
'(Warning: this directory is removed if it exists.)'
),
)
def validate_basetemp(path: str) -> str:
# GH 7119
msg = "basetemp must not be empty, the current working directory or any parent directory of it"
msg = 'basetemp must not be empty, the current working directory or any parent directory of it'
# empty path
if not path:
@ -270,8 +269,8 @@ def validate_basetemp(path: str) -> str:
def wrap_session(
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
) -> Union[int, ExitCode]:
config: Config, doit: Callable[[Config, Session], int | ExitCode | None],
) -> int | ExitCode:
"""Skeleton command line program."""
session = Session.from_config(config)
session.exitstatus = ExitCode.OK
@ -290,12 +289,12 @@ def wrap_session(
session.exitstatus = ExitCode.TESTS_FAILED
except (KeyboardInterrupt, exit.Exception):
excinfo = _pytest._code.ExceptionInfo.from_current()
exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED
exitstatus: int | ExitCode = ExitCode.INTERRUPTED
if isinstance(excinfo.value, exit.Exception):
if excinfo.value.returncode is not None:
exitstatus = excinfo.value.returncode
if initstate < 2:
sys.stderr.write(f"{excinfo.typename}: {excinfo.value.msg}\n")
sys.stderr.write(f'{excinfo.typename}: {excinfo.value.msg}\n')
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = exitstatus
except BaseException:
@ -306,10 +305,10 @@ def wrap_session(
except exit.Exception as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
sys.stderr.write(f'{type(exc).__name__}: {exc}\n')
else:
if isinstance(excinfo.value, SystemExit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
sys.stderr.write('mainloop: caught unexpected SystemExit!\n')
finally:
# Explicitly break reference cycle.
@ -318,21 +317,21 @@ def wrap_session(
if initstate >= 2:
try:
config.hook.pytest_sessionfinish(
session=session, exitstatus=session.exitstatus
session=session, exitstatus=session.exitstatus,
)
except exit.Exception as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
sys.stderr.write(f'{type(exc).__name__}: {exc}\n')
config._ensure_unconfigure()
return session.exitstatus
def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]:
def pytest_cmdline_main(config: Config) -> int | ExitCode:
return wrap_session(config, _main)
def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
def _main(config: Config, session: Session) -> int | ExitCode | None:
"""Default command line protocol for initialization, session,
running tests and reporting."""
config.hook.pytest_collection(session=session)
@ -345,15 +344,15 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
return None
def pytest_collection(session: "Session") -> None:
def pytest_collection(session: Session) -> None:
session.perform_collect()
def pytest_runtestloop(session: "Session") -> bool:
def pytest_runtestloop(session: Session) -> bool:
if session.testsfailed and not session.config.option.continue_on_collection_errors:
raise session.Interrupted(
"%d error%s during collection"
% (session.testsfailed, "s" if session.testsfailed != 1 else "")
'%d error%s during collection'
% (session.testsfailed, 's' if session.testsfailed != 1 else ''),
)
if session.config.option.collectonly:
@ -372,32 +371,32 @@ def pytest_runtestloop(session: "Session") -> bool:
def _in_venv(path: Path) -> bool:
"""Attempt to detect if ``path`` is the root of a Virtual Environment by
checking for the existence of the appropriate activate script."""
bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin")
bindir = path.joinpath('Scripts' if sys.platform.startswith('win') else 'bin')
try:
if not bindir.is_dir():
return False
except OSError:
return False
activates = (
"activate",
"activate.csh",
"activate.fish",
"Activate",
"Activate.bat",
"Activate.ps1",
'activate',
'activate.csh',
'activate.fish',
'Activate',
'Activate.bat',
'Activate.ps1',
)
return any(fname.name in activates for fname in bindir.iterdir())
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
if collection_path.name == "__pycache__":
def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None:
if collection_path.name == '__pycache__':
return True
ignore_paths = config._getconftest_pathlist(
"collect_ignore", path=collection_path.parent
'collect_ignore', path=collection_path.parent,
)
ignore_paths = ignore_paths or []
excludeopt = config.getoption("ignore")
excludeopt = config.getoption('ignore')
if excludeopt:
ignore_paths.extend(absolutepath(x) for x in excludeopt)
@ -405,22 +404,22 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
return True
ignore_globs = config._getconftest_pathlist(
"collect_ignore_glob", path=collection_path.parent
'collect_ignore_glob', path=collection_path.parent,
)
ignore_globs = ignore_globs or []
excludeglobopt = config.getoption("ignore_glob")
excludeglobopt = config.getoption('ignore_glob')
if excludeglobopt:
ignore_globs.extend(absolutepath(x) for x in excludeglobopt)
if any(fnmatch.fnmatch(str(collection_path), str(glob)) for glob in ignore_globs):
return True
allow_in_venv = config.getoption("collect_in_virtualenv")
allow_in_venv = config.getoption('collect_in_virtualenv')
if not allow_in_venv and _in_venv(collection_path):
return True
if collection_path.is_dir():
norecursepatterns = config.getini("norecursedirs")
norecursepatterns = config.getini('norecursedirs')
if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns):
return True
@ -428,13 +427,13 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
def pytest_collect_directory(
path: Path, parent: nodes.Collector
) -> Optional[nodes.Collector]:
path: Path, parent: nodes.Collector,
) -> nodes.Collector | None:
return Dir.from_parent(parent, path=path)
def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None:
deselect_prefixes = tuple(config.getoption("deselect") or [])
def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None:
deselect_prefixes = tuple(config.getoption('deselect') or [])
if not deselect_prefixes:
return
@ -469,7 +468,7 @@ class FSHookProxy:
class Interrupted(KeyboardInterrupt):
"""Signals that the test run was interrupted."""
__module__ = "builtins" # For py3.
__module__ = 'builtins' # For py3.
class Failed(Exception):
@ -478,7 +477,7 @@ class Failed(Exception):
@dataclasses.dataclass
class _bestrelpath_cache(Dict[Path, str]):
__slots__ = ("path",)
__slots__ = ('path',)
path: Path
@ -507,7 +506,7 @@ class Dir(nodes.Directory):
parent: nodes.Collector,
*,
path: Path,
) -> "Self":
) -> Self:
"""The public constructor.
:param parent: The parent collector of this Dir.
@ -515,9 +514,9 @@ class Dir(nodes.Directory):
"""
return super().from_parent(parent=parent, path=path)
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
config = self.config
col: Optional[nodes.Collector]
col: nodes.Collector | None
cols: Sequence[nodes.Collector]
ihook = self.ihook
for direntry in scandir(self.path):
@ -552,60 +551,60 @@ class Session(nodes.Collector):
_setupstate: SetupState
# Set on the session by fixtures.pytest_sessionstart.
_fixturemanager: FixtureManager
exitstatus: Union[int, ExitCode]
exitstatus: int | ExitCode
def __init__(self, config: Config) -> None:
super().__init__(
name="",
name='',
path=config.rootpath,
fspath=None,
parent=None,
config=config,
session=self,
nodeid="",
nodeid='',
)
self.testsfailed = 0
self.testscollected = 0
self._shouldstop: Union[bool, str] = False
self._shouldfail: Union[bool, str] = False
self.trace = config.trace.root.get("collection")
self._initialpaths: FrozenSet[Path] = frozenset()
self._initialpaths_with_parents: FrozenSet[Path] = frozenset()
self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = []
self._initial_parts: List[CollectionArgument] = []
self._collection_cache: Dict[nodes.Collector, CollectReport] = {}
self.items: List[nodes.Item] = []
self._shouldstop: bool | str = False
self._shouldfail: bool | str = False
self.trace = config.trace.root.get('collection')
self._initialpaths: frozenset[Path] = frozenset()
self._initialpaths_with_parents: frozenset[Path] = frozenset()
self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = []
self._initial_parts: list[CollectionArgument] = []
self._collection_cache: dict[nodes.Collector, CollectReport] = {}
self.items: list[nodes.Item] = []
self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath)
self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
self.config.pluginmanager.register(self, name="session")
self.config.pluginmanager.register(self, name='session')
@classmethod
def from_config(cls, config: Config) -> "Session":
def from_config(cls, config: Config) -> Session:
session: Session = cls._create(config=config)
return session
def __repr__(self) -> str:
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
return '<%s %s exitstatus=%r testsfailed=%d testscollected=%d>' % (
self.__class__.__name__,
self.name,
getattr(self, "exitstatus", "<UNSET>"),
getattr(self, 'exitstatus', '<UNSET>'),
self.testsfailed,
self.testscollected,
)
@property
def shouldstop(self) -> Union[bool, str]:
def shouldstop(self) -> bool | str:
return self._shouldstop
@shouldstop.setter
def shouldstop(self, value: Union[bool, str]) -> None:
def shouldstop(self, value: bool | str) -> None:
# The runner checks shouldfail and assumes that if it is set we are
# definitely stopping, so prevent unsetting it.
if value is False and self._shouldstop:
warnings.warn(
PytestWarning(
"session.shouldstop cannot be unset after it has been set; ignoring."
'session.shouldstop cannot be unset after it has been set; ignoring.',
),
stacklevel=2,
)
@ -613,17 +612,17 @@ class Session(nodes.Collector):
self._shouldstop = value
@property
def shouldfail(self) -> Union[bool, str]:
def shouldfail(self) -> bool | str:
return self._shouldfail
@shouldfail.setter
def shouldfail(self, value: Union[bool, str]) -> None:
def shouldfail(self, value: bool | str) -> None:
# The runner checks shouldfail and assumes that if it is set we are
# definitely stopping, so prevent unsetting it.
if value is False and self._shouldfail:
warnings.warn(
PytestWarning(
"session.shouldfail cannot be unset after it has been set; ignoring."
'session.shouldfail cannot be unset after it has been set; ignoring.',
),
stacklevel=2,
)
@ -651,19 +650,19 @@ class Session(nodes.Collector):
@hookimpl(tryfirst=True)
def pytest_runtest_logreport(
self, report: Union[TestReport, CollectReport]
self, report: TestReport | CollectReport,
) -> None:
if report.failed and not hasattr(report, "wasxfail"):
if report.failed and not hasattr(report, 'wasxfail'):
self.testsfailed += 1
maxfail = self.config.getvalue("maxfail")
maxfail = self.config.getvalue('maxfail')
if maxfail and self.testsfailed >= maxfail:
self.shouldfail = "stopping after %d failures" % (self.testsfailed)
self.shouldfail = 'stopping after %d failures' % (self.testsfailed)
pytest_collectreport = pytest_runtest_logreport
def isinitpath(
self,
path: Union[str, "os.PathLike[str]"],
path: str | os.PathLike[str],
*,
with_parents: bool = False,
) -> bool:
@ -685,7 +684,7 @@ class Session(nodes.Collector):
else:
return path_ in self._initialpaths
def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay:
def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
# Optimization: Path(Path(...)) is much slower than isinstance.
path = fspath if isinstance(fspath, Path) else Path(fspath)
pm = self.config.pluginmanager
@ -705,7 +704,7 @@ class Session(nodes.Collector):
def _collect_path(
self,
path: Path,
path_cache: Dict[Path, Sequence[nodes.Collector]],
path_cache: dict[Path, Sequence[nodes.Collector]],
) -> Sequence[nodes.Collector]:
"""Create a Collector for the given path.
@ -717,8 +716,8 @@ class Session(nodes.Collector):
if path.is_dir():
ihook = self.gethookproxy(path.parent)
col: Optional[nodes.Collector] = ihook.pytest_collect_directory(
path=path, parent=self
col: nodes.Collector | None = ihook.pytest_collect_directory(
path=path, parent=self,
)
cols: Sequence[nodes.Collector] = (col,) if col is not None else ()
@ -735,19 +734,19 @@ class Session(nodes.Collector):
@overload
def perform_collect(
self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ...
self, args: Sequence[str] | None = ..., genitems: Literal[True] = ...,
) -> Sequence[nodes.Item]:
...
@overload
def perform_collect(
self, args: Optional[Sequence[str]] = ..., genitems: bool = ...
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
self, args: Sequence[str] | None = ..., genitems: bool = ...,
) -> Sequence[nodes.Item | nodes.Collector]:
...
def perform_collect(
self, args: Optional[Sequence[str]] = None, genitems: bool = True
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
self, args: Sequence[str] | None = None, genitems: bool = True,
) -> Sequence[nodes.Item | nodes.Collector]:
"""Perform the collection phase for this session.
This is called by the default :hook:`pytest_collection` hook
@ -764,7 +763,7 @@ class Session(nodes.Collector):
if args is None:
args = self.config.args
self.trace("perform_collect", self, args)
self.trace('perform_collect', self, args)
self.trace.root.indent += 1
hook = self.config.hook
@ -773,10 +772,10 @@ class Session(nodes.Collector):
self._initial_parts = []
self._collection_cache = {}
self.items = []
items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items
items: Sequence[nodes.Item | nodes.Collector] = self.items
try:
initialpaths: List[Path] = []
initialpaths_with_parents: List[Path] = []
initialpaths: list[Path] = []
initialpaths_with_parents: list[Path] = []
for arg in args:
collection_argument = resolve_collection_argument(
self.config.invocation_params.dir,
@ -798,10 +797,10 @@ class Session(nodes.Collector):
for arg, collectors in self._notfound:
if collectors:
errors.append(
f"not found: {arg}\n(no match in any of {collectors!r})"
f'not found: {arg}\n(no match in any of {collectors!r})',
)
else:
errors.append(f"found no collectors for {arg}")
errors.append(f'found no collectors for {arg}')
raise UsageError(*errors)
@ -814,7 +813,7 @@ class Session(nodes.Collector):
self.config.pluginmanager.check_pending()
hook.pytest_collection_modifyitems(
session=self, config=self.config, items=items
session=self, config=self.config, items=items,
)
finally:
self._notfound = []
@ -831,7 +830,7 @@ class Session(nodes.Collector):
self,
node: nodes.Collector,
handle_dupes: bool = True,
) -> Tuple[CollectReport, bool]:
) -> tuple[CollectReport, bool]:
if node in self._collection_cache and handle_dupes:
rep = self._collection_cache[node]
return rep, True
@ -840,16 +839,16 @@ class Session(nodes.Collector):
self._collection_cache[node] = rep
return rep, False
def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
# This is a cache for the root directories of the initial paths.
# We can't use collection_cache for Session because of its special
# role as the bootstrapping collector.
path_cache: Dict[Path, Sequence[nodes.Collector]] = {}
path_cache: dict[Path, Sequence[nodes.Collector]] = {}
pm = self.config.pluginmanager
for collection_argument in self._initial_parts:
self.trace("processing argument", collection_argument)
self.trace('processing argument', collection_argument)
self.trace.root.indent += 1
argpath = collection_argument.path
@ -858,7 +857,7 @@ class Session(nodes.Collector):
# resolve_collection_argument() ensures this.
if argpath.is_dir():
assert not names, f"invalid arg {(argpath, names)!r}"
assert not names, f'invalid arg {(argpath, names)!r}'
paths = [argpath]
# Add relevant parents of the path, from the root, e.g.
@ -872,7 +871,7 @@ class Session(nodes.Collector):
else:
# For --pyargs arguments, only consider paths matching the module
# name. Paths beyond the package hierarchy are not included.
module_name_parts = module_name.split(".")
module_name_parts = module_name.split('.')
for i, path in enumerate(argpath.parents, 2):
if i > len(module_name_parts) or path.stem != module_name_parts[-i]:
break
@ -882,8 +881,8 @@ class Session(nodes.Collector):
# and discarding all nodes which don't match the level's part.
any_matched_in_initial_part = False
notfound_collectors = []
work: List[
Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]]
work: list[
tuple[nodes.Collector | nodes.Item, list[Path | str]]
] = [(self, [*paths, *names])]
while work:
matchnode, matchparts = work.pop()
@ -901,7 +900,7 @@ class Session(nodes.Collector):
# Collect this level of matching.
# Collecting Session (self) is done directly to avoid endless
# recursion to this function.
subnodes: Sequence[Union[nodes.Collector, nodes.Item]]
subnodes: Sequence[nodes.Collector | nodes.Item]
if isinstance(matchnode, Session):
assert isinstance(matchparts[0], Path)
subnodes = matchnode._collect_path(matchparts[0], path_cache)
@ -909,9 +908,9 @@ class Session(nodes.Collector):
# For backward compat, files given directly multiple
# times on the command line should not be deduplicated.
handle_dupes = not (
len(matchparts) == 1
and isinstance(matchparts[0], Path)
and matchparts[0].is_file()
len(matchparts) == 1 and
isinstance(matchparts[0], Path) and
matchparts[0].is_file()
)
rep, duplicate = self._collect_one_node(matchnode, handle_dupes)
if not duplicate and not rep.passed:
@ -929,15 +928,15 @@ class Session(nodes.Collector):
# Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
if isinstance(matchparts[0], Path):
is_match = node.path == matchparts[0]
if sys.platform == "win32" and not is_match:
if sys.platform == 'win32' and not is_match:
# In case the file paths do not match, fallback to samefile() to
# account for short-paths on Windows (#11895).
same_file = os.path.samefile(node.path, matchparts[0])
# We don't want to match links to the current node,
# otherwise we would match the same file more than once (#12039).
is_match = same_file and (
os.path.islink(node.path)
== os.path.islink(matchparts[0])
os.path.islink(node.path) ==
os.path.islink(matchparts[0])
)
# Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
@ -945,8 +944,8 @@ class Session(nodes.Collector):
# TODO: Remove parametrized workaround once collection structure contains
# parametrization.
is_match = (
node.name == matchparts[0]
or node.name.split("[")[0] == matchparts[0]
node.name == matchparts[0] or
node.name.split('[')[0] == matchparts[0]
)
if is_match:
work.append((node, matchparts[1:]))
@ -956,21 +955,21 @@ class Session(nodes.Collector):
notfound_collectors.append(matchnode)
if not any_matched_in_initial_part:
report_arg = "::".join((str(argpath), *names))
report_arg = '::'.join((str(argpath), *names))
self._notfound.append((report_arg, notfound_collectors))
self.trace.root.indent -= 1
def genitems(
self, node: Union[nodes.Item, nodes.Collector]
self, node: nodes.Item | nodes.Collector,
) -> Iterator[nodes.Item]:
self.trace("genitems", node)
self.trace('genitems', node)
if isinstance(node, nodes.Item):
node.ihook.pytest_itemcollected(item=node)
yield node
else:
assert isinstance(node, nodes.Collector)
keepduplicates = self.config.getoption("keepduplicates")
keepduplicates = self.config.getoption('keepduplicates')
# For backward compat, dedup only applies to files.
handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
rep, duplicate = self._collect_one_node(node, handle_dupes)
@ -983,7 +982,7 @@ class Session(nodes.Collector):
node.ihook.pytest_collectreport(report=rep)
def search_pypath(module_name: str) -> Optional[str]:
def search_pypath(module_name: str) -> str | None:
"""Search sys.path for the given a dotted module name, and return its file
system path if found."""
try:
@ -993,7 +992,7 @@ def search_pypath(module_name: str) -> Optional[str]:
# ValueError: not a module name
except (AttributeError, ImportError, ValueError):
return None
if spec is None or spec.origin is None or spec.origin == "namespace":
if spec is None or spec.origin is None or spec.origin == 'namespace':
return None
elif spec.submodule_search_locations:
return os.path.dirname(spec.origin)
@ -1007,11 +1006,11 @@ class CollectionArgument:
path: Path
parts: Sequence[str]
module_name: Optional[str]
module_name: str | None
def resolve_collection_argument(
invocation_path: Path, arg: str, *, as_pypath: bool = False
invocation_path: Path, arg: str, *, as_pypath: bool = False,
) -> CollectionArgument:
"""Parse path arguments optionally containing selection parts and return (fspath, names).
@ -1045,10 +1044,10 @@ def resolve_collection_argument(
If the path doesn't exist, raise UsageError.
If the path is a directory and selection parts are present, raise UsageError.
"""
base, squacket, rest = str(arg).partition("[")
strpath, *parts = base.split("::")
base, squacket, rest = str(arg).partition('[')
strpath, *parts = base.split('::')
if parts:
parts[-1] = f"{parts[-1]}{squacket}{rest}"
parts[-1] = f'{parts[-1]}{squacket}{rest}'
module_name = None
if as_pypath:
pyarg_strpath = search_pypath(strpath)
@ -1059,16 +1058,16 @@ def resolve_collection_argument(
fspath = absolutepath(fspath)
if not safe_exists(fspath):
msg = (
"module or package not found: {arg} (missing __init__.py?)"
'module or package not found: {arg} (missing __init__.py?)'
if as_pypath
else "file or directory not found: {arg}"
else 'file or directory not found: {arg}'
)
raise UsageError(msg.format(arg=arg))
if parts and fspath.is_dir():
msg = (
"package argument cannot contain :: selection parts: {arg}"
'package argument cannot contain :: selection parts: {arg}'
if as_pypath
else "directory argument cannot contain :: selection parts: {arg}"
else 'directory argument cannot contain :: selection parts: {arg}'
)
raise UsageError(msg.format(arg=arg))
return CollectionArgument(