aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/config
diff options
context:
space:
mode:
authorshadchin <shadchin@yandex-team.ru>2022-02-10 16:44:30 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:44:30 +0300
commit2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch)
tree012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/pytest/py3/_pytest/config
parent6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff)
downloadydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/config')
-rw-r--r--contrib/python/pytest/py3/_pytest/config/__init__.py2110
-rw-r--r--contrib/python/pytest/py3/_pytest/config/argparsing.py528
-rw-r--r--contrib/python/pytest/py3/_pytest/config/exceptions.py12
-rw-r--r--contrib/python/pytest/py3/_pytest/config/findpaths.py318
4 files changed, 1484 insertions, 1484 deletions
diff --git a/contrib/python/pytest/py3/_pytest/config/__init__.py b/contrib/python/pytest/py3/_pytest/config/__init__.py
index bd9e2883f9..fc04943216 100644
--- a/contrib/python/pytest/py3/_pytest/config/__init__.py
+++ b/contrib/python/pytest/py3/_pytest/config/__init__.py
@@ -1,236 +1,236 @@
-"""Command line options, ini-file and conftest.py processing."""
+"""Command line options, ini-file and conftest.py processing."""
import argparse
-import collections.abc
-import contextlib
+import collections.abc
+import contextlib
import copy
-import enum
+import enum
import inspect
import os
-import re
+import re
import shlex
import sys
import types
import warnings
-from functools import lru_cache
-from pathlib import Path
-from types import TracebackType
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generator
-from typing import IO
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import TextIO
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import Union
-
-import attr
+from functools import lru_cache
+from pathlib import Path
+from types import TracebackType
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Generator
+from typing import IO
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import Optional
+from typing import Sequence
+from typing import Set
+from typing import TextIO
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import Union
+
+import attr
import py
from pluggy import HookimplMarker
from pluggy import HookspecMarker
from pluggy import PluginManager
import _pytest._code
-import _pytest.deprecated
-import _pytest.hookspec
-from .exceptions import PrintHelp as PrintHelp
-from .exceptions import UsageError as UsageError
+import _pytest.deprecated
+import _pytest.hookspec
+from .exceptions import PrintHelp as PrintHelp
+from .exceptions import UsageError as UsageError
from .findpaths import determine_setup
from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback
-from _pytest._io import TerminalWriter
-from _pytest.compat import final
-from _pytest.compat import importlib_metadata
-from _pytest.outcomes import fail
+from _pytest._io import TerminalWriter
+from _pytest.compat import final
+from _pytest.compat import importlib_metadata
+from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
-from _pytest.pathlib import bestrelpath
-from _pytest.pathlib import import_path
-from _pytest.pathlib import ImportMode
-from _pytest.store import Store
-from _pytest.warning_types import PytestConfigWarning
-
-if TYPE_CHECKING:
-
- from _pytest._code.code import _TracebackStyle
- from _pytest.terminal import TerminalReporter
- from .argparsing import Argument
-
-
-_PluggyPlugin = object
-"""A type to represent plugin objects.
-
-Plugins can be any namespace, so we can't narrow it down much, but we use an
-alias to make the intent clear.
-
-Ideally this type would be provided by pluggy itself.
-"""
-
-
+from _pytest.pathlib import bestrelpath
+from _pytest.pathlib import import_path
+from _pytest.pathlib import ImportMode
+from _pytest.store import Store
+from _pytest.warning_types import PytestConfigWarning
+
+if TYPE_CHECKING:
+
+ from _pytest._code.code import _TracebackStyle
+ from _pytest.terminal import TerminalReporter
+ from .argparsing import Argument
+
+
+_PluggyPlugin = object
+"""A type to represent plugin objects.
+
+Plugins can be any namespace, so we can't narrow it down much, but we use an
+alias to make the intent clear.
+
+Ideally this type would be provided by pluggy itself.
+"""
+
+
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
-@final
-class ExitCode(enum.IntEnum):
- """Encodes the valid exit codes by pytest.
-
- Currently users and plugins may supply other exit codes as well.
-
- .. versionadded:: 5.0
- """
-
- #: Tests passed.
- OK = 0
- #: Tests failed.
- TESTS_FAILED = 1
- #: pytest was interrupted.
- INTERRUPTED = 2
- #: An internal error got in the way.
- INTERNAL_ERROR = 3
- #: pytest was misused.
- USAGE_ERROR = 4
- #: pytest couldn't find tests.
- NO_TESTS_COLLECTED = 5
-
-
+@final
+class ExitCode(enum.IntEnum):
+ """Encodes the valid exit codes by pytest.
+
+ Currently users and plugins may supply other exit codes as well.
+
+ .. versionadded:: 5.0
+ """
+
+ #: Tests passed.
+ OK = 0
+ #: Tests failed.
+ TESTS_FAILED = 1
+ #: pytest was interrupted.
+ INTERRUPTED = 2
+ #: An internal error got in the way.
+ INTERNAL_ERROR = 3
+ #: pytest was misused.
+ USAGE_ERROR = 4
+ #: pytest couldn't find tests.
+ NO_TESTS_COLLECTED = 5
+
+
class ConftestImportFailure(Exception):
- def __init__(
- self,
- path: py.path.local,
- excinfo: Tuple[Type[Exception], Exception, TracebackType],
- ) -> None:
- super().__init__(path, excinfo)
+ def __init__(
+ self,
+ path: py.path.local,
+ excinfo: Tuple[Type[Exception], Exception, TracebackType],
+ ) -> None:
+ super().__init__(path, excinfo)
self.path = path
- self.excinfo = excinfo
-
- def __str__(self) -> str:
- return "{}: {} (from {})".format(
- self.excinfo[0].__name__, self.excinfo[1], self.path
- )
-
-
-def filter_traceback_for_conftest_import_failure(
- entry: _pytest._code.TracebackEntry,
-) -> bool:
- """Filter tracebacks entries which point to pytest internals or importlib.
-
- Make a special case for importlib because we use it to import test modules and conftest files
- in _pytest.pathlib.import_path.
- """
- return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
+ self.excinfo = excinfo
+ def __str__(self) -> str:
+ return "{}: {} (from {})".format(
+ self.excinfo[0].__name__, self.excinfo[1], self.path
+ )
-def main(
- args: Optional[Union[List[str], py.path.local]] = None,
- plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
-) -> Union[int, ExitCode]:
- """Perform an in-process test run.
- :param args: List of command line arguments.
- :param plugins: List of plugin objects to be auto-registered during initialization.
+def filter_traceback_for_conftest_import_failure(
+ entry: _pytest._code.TracebackEntry,
+) -> bool:
+ """Filter tracebacks entries which point to pytest internals or importlib.
- :returns: An exit code.
+ Make a special case for importlib because we use it to import test modules and conftest files
+ in _pytest.pathlib.import_path.
"""
+ return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
+
+
+def main(
+ args: Optional[Union[List[str], py.path.local]] = None,
+ plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
+) -> Union[int, ExitCode]:
+ """Perform an in-process test run.
+
+ :param args: List of command line arguments.
+ :param plugins: List of plugin objects to be auto-registered during initialization.
+
+ :returns: An exit code.
+ """
try:
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure as e:
exc_info = ExceptionInfo(e.excinfo)
- tw = TerminalWriter(sys.stderr)
- tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
- exc_info.traceback = exc_info.traceback.filter(
- filter_traceback_for_conftest_import_failure
+ tw = TerminalWriter(sys.stderr)
+ tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
+ exc_info.traceback = exc_info.traceback.filter(
+ filter_traceback_for_conftest_import_failure
)
exc_repr = (
exc_info.getrepr(style="short", chain=False)
if exc_info.traceback
else exc_info.exconly()
)
- formatted_tb = str(exc_repr)
+ formatted_tb = str(exc_repr)
for line in formatted_tb.splitlines():
tw.line(line.rstrip(), red=True)
- return ExitCode.USAGE_ERROR
+ return ExitCode.USAGE_ERROR
else:
try:
- ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
- config=config
- )
- try:
- return ExitCode(ret)
- except ValueError:
- return ret
+ ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
+ config=config
+ )
+ try:
+ return ExitCode(ret)
+ except ValueError:
+ return ret
finally:
config._ensure_unconfigure()
except UsageError as e:
- tw = TerminalWriter(sys.stderr)
+ tw = TerminalWriter(sys.stderr)
for msg in e.args:
- tw.line(f"ERROR: {msg}\n", red=True)
- return ExitCode.USAGE_ERROR
-
-
-def console_main() -> int:
- """The CLI entry point of pytest.
-
- This function is not meant for programmable use; use `main()` instead.
- """
- # https://docs.python.org/3/library/signal.html#note-on-sigpipe
- try:
- code = main()
- sys.stdout.flush()
- return code
- except BrokenPipeError:
- # Python flushes standard streams on exit; redirect remaining output
- # to devnull to avoid another BrokenPipeError at shutdown
- devnull = os.open(os.devnull, os.O_WRONLY)
- os.dup2(devnull, sys.stdout.fileno())
- return 1 # Python exits with error code 1 on EPIPE
-
-
-class cmdline: # compatibility namespace
+ tw.line(f"ERROR: {msg}\n", red=True)
+ return ExitCode.USAGE_ERROR
+
+
+def console_main() -> int:
+ """The CLI entry point of pytest.
+
+ This function is not meant for programmable use; use `main()` instead.
+ """
+ # https://docs.python.org/3/library/signal.html#note-on-sigpipe
+ try:
+ code = main()
+ sys.stdout.flush()
+ return code
+ except BrokenPipeError:
+ # Python flushes standard streams on exit; redirect remaining output
+ # to devnull to avoid another BrokenPipeError at shutdown
+ devnull = os.open(os.devnull, os.O_WRONLY)
+ os.dup2(devnull, sys.stdout.fileno())
+ return 1 # Python exits with error code 1 on EPIPE
+
+
+class cmdline: # compatibility namespace
main = staticmethod(main)
-def filename_arg(path: str, optname: str) -> str:
- """Argparse type validator for filename arguments.
+def filename_arg(path: str, optname: str) -> str:
+ """Argparse type validator for filename arguments.
- :path: Path of filename.
- :optname: Name of the option.
+ :path: Path of filename.
+ :optname: Name of the option.
"""
if os.path.isdir(path):
- raise UsageError(f"{optname} must be a filename, given: {path}")
+ raise UsageError(f"{optname} must be a filename, given: {path}")
return path
-def directory_arg(path: str, optname: str) -> str:
+def directory_arg(path: str, optname: str) -> str:
"""Argparse type validator for directory arguments.
- :path: Path of directory.
- :optname: Name of the option.
+ :path: Path of directory.
+ :optname: Name of the option.
"""
if not os.path.isdir(path):
- raise UsageError(f"{optname} must be a directory, given: {path}")
+ raise UsageError(f"{optname} must be a directory, given: {path}")
return path
-# Plugins that cannot be disabled via "-p no:X" currently.
-essential_plugins = (
+# Plugins that cannot be disabled via "-p no:X" currently.
+essential_plugins = (
"mark",
"main",
"runner",
- "fixtures",
- "helpconfig", # Provides -p.
-)
-
-default_plugins = essential_plugins + (
+ "fixtures",
+ "helpconfig", # Provides -p.
+)
+
+default_plugins = essential_plugins + (
"python",
- "terminal",
+ "terminal",
"debugging",
"unittest",
"capture",
@@ -250,41 +250,41 @@ default_plugins = essential_plugins + (
"stepwise",
"warnings",
"logging",
- "reports",
- *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
- "faulthandler",
+ "reports",
+ *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
+ "faulthandler",
)
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
-builtin_plugins.add("pytester_assertions")
+builtin_plugins.add("pytester_assertions")
-def get_config(
- args: Optional[List[str]] = None,
- plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
-) -> "Config":
+def get_config(
+ args: Optional[List[str]] = None,
+ plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
+) -> "Config":
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
- config = Config(
- pluginmanager,
- invocation_params=Config.InvocationParams(
- args=args or (), plugins=plugins, dir=Path.cwd(),
- ),
- )
-
- if args is not None:
- # Handle any "-p no:plugin" args.
- pluginmanager.consider_preparse(args, exclude_only=True)
-
+ config = Config(
+ pluginmanager,
+ invocation_params=Config.InvocationParams(
+ args=args or (), plugins=plugins, dir=Path.cwd(),
+ ),
+ )
+
+ if args is not None:
+ # Handle any "-p no:plugin" args.
+ pluginmanager.consider_preparse(args, exclude_only=True)
+
for spec in default_plugins:
pluginmanager.import_plugin(spec)
-
+
return config
-def get_plugin_manager() -> "PytestPluginManager":
- """Obtain a new instance of the
+def get_plugin_manager() -> "PytestPluginManager":
+ """Obtain a new instance of the
:py:class:`_pytest.config.PytestPluginManager`, with default plugins
already loaded.
@@ -294,76 +294,76 @@ def get_plugin_manager() -> "PytestPluginManager":
return get_config().pluginmanager
-def _prepareconfig(
- args: Optional[Union[py.path.local, List[str]]] = None,
- plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
-) -> "Config":
+def _prepareconfig(
+ args: Optional[Union[py.path.local, List[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)]
- 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)))
+ 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)))
- config = get_config(args, plugins)
+ config = get_config(args, plugins)
pluginmanager = config.pluginmanager
try:
if plugins:
for plugin in plugins:
- if isinstance(plugin, str):
+ if isinstance(plugin, str):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
- config = pluginmanager.hook.pytest_cmdline_parse(
+ config = pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args
)
- return config
+ return config
except BaseException:
config._ensure_unconfigure()
raise
-@final
+@final
class PytestPluginManager(PluginManager):
- """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
- additional pytest-specific functionality:
+ """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
+ additional pytest-specific functionality:
- * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
- ``pytest_plugins`` global variables found in plugins being loaded.
- * ``conftest.py`` loading during start-up.
+ * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
+ ``pytest_plugins`` global variables found in plugins being loaded.
+ * ``conftest.py`` loading during start-up.
"""
- def __init__(self) -> None:
- import _pytest.assertion
-
- super().__init__("pytest")
- # The objects are module objects, only used generically.
- self._conftest_plugins: Set[types.ModuleType] = set()
-
- # State related to local conftest plugins.
- self._dirpath2confmods: Dict[py.path.local, List[types.ModuleType]] = {}
- self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
- self._confcutdir: Optional[py.path.local] = None
+ def __init__(self) -> None:
+ import _pytest.assertion
+
+ super().__init__("pytest")
+ # The objects are module objects, only used generically.
+ self._conftest_plugins: Set[types.ModuleType] = set()
+
+ # State related to local conftest plugins.
+ self._dirpath2confmods: Dict[py.path.local, List[types.ModuleType]] = {}
+ self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
+ self._confcutdir: Optional[py.path.local] = None
self._noconftest = False
- self._duplicatepaths: Set[py.path.local] = set()
-
- # plugins that were explicitly skipped with pytest.skip
- # list of (module name, skip reason)
- # previously we would issue a warning when a plugin was skipped, but
- # since we refactored warnings as first citizens of Config, they are
- # just stored here to be used later.
- self.skipped_plugins: List[Tuple[str, str]] = []
-
+ self._duplicatepaths: Set[py.path.local] = set()
+
+ # plugins that were explicitly skipped with pytest.skip
+ # list of (module name, skip reason)
+ # previously we would issue a warning when a plugin was skipped, but
+ # since we refactored warnings as first citizens of Config, they are
+ # just stored here to be used later.
+ self.skipped_plugins: List[Tuple[str, str]] = []
+
self.add_hookspecs(_pytest.hookspec)
self.register(self)
if os.environ.get("PYTEST_DEBUG"):
- err: IO[str] = sys.stderr
- encoding: str = getattr(err, "encoding", "utf8")
+ err: IO[str] = sys.stderr
+ encoding: str = getattr(err, "encoding", "utf8")
try:
- err = open(
- os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding,
- )
+ err = open(
+ os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding,
+ )
except Exception:
pass
self.trace.root.setwriter(err.write)
@@ -371,69 +371,69 @@ class PytestPluginManager(PluginManager):
# Config._consider_importhook will set a real object if required.
self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
- # Used to know when we are importing conftests after the pytest_configure stage.
+ # Used to know when we are importing conftests after the pytest_configure stage.
self._configured = False
- def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
- # pytest hooks are always prefixed with "pytest_",
+ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
+ # pytest hooks are always prefixed with "pytest_",
# so we avoid accessing possibly non-readable attributes
- # (see issue #1073).
+ # (see issue #1073).
if not name.startswith("pytest_"):
return
- # Ignore names which can not be hooks.
- if name == "pytest_plugins":
+ # Ignore names which can not be hooks.
+ if name == "pytest_plugins":
return
method = getattr(plugin, name)
- opts = super().parse_hookimpl_opts(plugin, name)
+ opts = super().parse_hookimpl_opts(plugin, name)
- # Consider only actual functions for hooks (#3775).
+ # Consider only actual functions for hooks (#3775).
if not inspect.isroutine(method):
return
- # Collect unmarked hooks as long as they have the `pytest_' prefix.
+ # Collect unmarked hooks as long as they have the `pytest_' prefix.
if opts is None and name.startswith("pytest_"):
opts = {}
- if opts is not None:
- # TODO: DeprecationWarning, people should use hookimpl
- # https://github.com/pytest-dev/pytest/issues/4562
- known_marks = {m.name for m in getattr(method, "pytestmark", [])}
+ if opts is not None:
+ # TODO: DeprecationWarning, people should use hookimpl
+ # https://github.com/pytest-dev/pytest/issues/4562
+ known_marks = {m.name for m in getattr(method, "pytestmark", [])}
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
- opts.setdefault(name, hasattr(method, name) or name in known_marks)
+ opts.setdefault(name, hasattr(method, name) or name in known_marks)
return opts
- def parse_hookspec_opts(self, module_or_class, name: str):
- opts = super().parse_hookspec_opts(module_or_class, name)
+ def parse_hookspec_opts(self, module_or_class, name: str):
+ opts = super().parse_hookspec_opts(module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
-
+
if name.startswith("pytest_"):
- # todo: deprecate hookspec hacks
- # https://github.com/pytest-dev/pytest/issues/4562
- known_marks = {m.name for m in getattr(method, "pytestmark", [])}
+ # todo: deprecate hookspec hacks
+ # https://github.com/pytest-dev/pytest/issues/4562
+ known_marks = {m.name for m in getattr(method, "pytestmark", [])}
opts = {
- "firstresult": hasattr(method, "firstresult")
- or "firstresult" in known_marks,
- "historic": hasattr(method, "historic")
- or "historic" in known_marks,
+ "firstresult": hasattr(method, "firstresult")
+ or "firstresult" in known_marks,
+ "historic": hasattr(method, "historic")
+ or "historic" in known_marks,
}
return opts
- def register(
- self, plugin: _PluggyPlugin, name: Optional[str] = None
- ) -> Optional[str]:
- if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
- warnings.warn(
- PytestConfigWarning(
- "{} plugin has been merged into the core, "
- "please remove it from your requirements.".format(
- name.replace("_", "-")
- )
+ def register(
+ self, plugin: _PluggyPlugin, name: Optional[str] = None
+ ) -> Optional[str]:
+ if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
+ warnings.warn(
+ PytestConfigWarning(
+ "{} plugin has been merged into the core, "
+ "please remove it from your requirements.".format(
+ name.replace("_", "-")
+ )
)
)
- return None
- ret: Optional[str] = super().register(plugin, name)
+ return None
+ ret: Optional[str] = super().register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self)
@@ -443,19 +443,19 @@ class PytestPluginManager(PluginManager):
self.consider_module(plugin)
return ret
- def getplugin(self, name: str):
- # Support deprecated naming because plugins (xdist e.g.) use it.
- plugin: Optional[_PluggyPlugin] = self.get_plugin(name)
- return plugin
+ def getplugin(self, name: str):
+ # Support deprecated naming because plugins (xdist e.g.) use it.
+ plugin: Optional[_PluggyPlugin] = self.get_plugin(name)
+ return plugin
- def hasplugin(self, name: str) -> bool:
- """Return whether a plugin with the given name is registered."""
+ def hasplugin(self, name: str) -> bool:
+ """Return whether a plugin with the given name is registered."""
return bool(self.get_plugin(name))
- def pytest_configure(self, config: "Config") -> None:
- """:meta private:"""
+ def pytest_configure(self, config: "Config") -> None:
+ """:meta private:"""
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
- # we should remove tryfirst/trylast as markers.
+ # we should remove tryfirst/trylast as markers.
config.addinivalue_line(
"markers",
"tryfirst: mark a hook implementation function such that the "
@@ -469,15 +469,15 @@ class PytestPluginManager(PluginManager):
self._configured = True
#
- # Internal API for local conftest plugin handling.
+ # Internal API for local conftest plugin handling.
#
- def _set_initial_conftests(self, namespace: argparse.Namespace) -> None:
- """Load initial conftest files given a preparsed "namespace".
-
- As conftest files may add their own command line options which have
- arguments ('--my-opt somepath') we might get some false positives.
- All builtin and 3rd party plugins will have been loaded, however, so
- common options will not confuse our logic here.
+ def _set_initial_conftests(self, namespace: argparse.Namespace) -> None:
+ """Load initial conftest files given a preparsed "namespace".
+
+ As conftest files may add their own command line options which have
+ arguments ('--my-opt somepath') we might get some false positives.
+ All builtin and 3rd party plugins will have been loaded, however, so
+ common options will not confuse our logic here.
"""
current = py.path.local()
self._confcutdir = (
@@ -489,33 +489,33 @@ class PytestPluginManager(PluginManager):
self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir
foundanchor = False
- for testpath in testpaths:
- path = str(testpath)
+ for testpath in testpaths:
+ path = str(testpath)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
- if anchor.exists(): # we found some file object
- self._try_load_conftest(anchor, namespace.importmode)
+ if anchor.exists(): # we found some file object
+ self._try_load_conftest(anchor, namespace.importmode)
foundanchor = True
if not foundanchor:
- self._try_load_conftest(current, namespace.importmode)
+ self._try_load_conftest(current, namespace.importmode)
- def _try_load_conftest(
- self, anchor: py.path.local, importmode: Union[str, ImportMode]
- ) -> None:
- self._getconftestmodules(anchor, importmode)
+ def _try_load_conftest(
+ self, anchor: py.path.local, importmode: Union[str, ImportMode]
+ ) -> None:
+ self._getconftestmodules(anchor, importmode)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
- self._getconftestmodules(x, importmode)
+ self._getconftestmodules(x, importmode)
@lru_cache(maxsize=128)
- def _getconftestmodules(
- self, path: py.path.local, importmode: Union[str, ImportMode],
- ) -> List[types.ModuleType]:
+ def _getconftestmodules(
+ self, path: py.path.local, importmode: Union[str, ImportMode],
+ ) -> List[types.ModuleType]:
if self._noconftest:
return []
@@ -524,24 +524,24 @@ class PytestPluginManager(PluginManager):
else:
directory = path
- # XXX these days we may rather want to use config.rootpath
+ # XXX these days we may rather want to use config.rootpath
# and allow users to opt into looking into the rootdir parent
- # directories instead of requiring to specify confcutdir.
+ # directories instead of requiring to specify confcutdir.
clist = []
- for parent in directory.parts():
+ for parent in directory.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.isfile():
- mod = self._importconftest(conftestpath, importmode)
+ mod = self._importconftest(conftestpath, importmode)
clist.append(mod)
self._dirpath2confmods[directory] = clist
return clist
- def _rget_with_confmod(
- self, name: str, path: py.path.local, importmode: Union[str, ImportMode],
- ) -> Tuple[types.ModuleType, Any]:
- modules = self._getconftestmodules(path, importmode)
+ def _rget_with_confmod(
+ self, name: str, path: py.path.local, importmode: Union[str, ImportMode],
+ ) -> Tuple[types.ModuleType, Any]:
+ modules = self._getconftestmodules(path, importmode)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
@@ -549,98 +549,98 @@ class PytestPluginManager(PluginManager):
continue
raise KeyError(name)
- def _importconftest(
- self, conftestpath: py.path.local, importmode: Union[str, ImportMode],
- ) -> types.ModuleType:
- # Use a resolved Path object as key to avoid loading the same conftest
- # twice with build systems that create build directories containing
- # symlinks to actual files.
- # Using Path().resolve() is better than py.path.realpath because
- # it resolves to the correct path/drive in case-insensitive file systems (#5792)
- key = Path(str(conftestpath)).resolve()
-
- with contextlib.suppress(KeyError):
- return self._conftestpath2mod[key]
-
- pkgpath = conftestpath.pypkgpath()
- if pkgpath is None:
- _ensure_removed_sysmodule(conftestpath.purebasename)
-
- try:
- mod = import_path(conftestpath, mode=importmode)
- except Exception as e:
- assert e.__traceback__ is not None
- exc_info = (type(e), e, e.__traceback__)
- raise ConftestImportFailure(conftestpath, exc_info) from e
-
- self._check_non_top_pytest_plugins(mod, conftestpath)
-
- self._conftest_plugins.add(mod)
- self._conftestpath2mod[key] = mod
- dirpath = conftestpath.dirpath()
- if dirpath in self._dirpath2confmods:
- for path, mods in self._dirpath2confmods.items():
- if path and path.relto(dirpath) or path == dirpath:
- assert mod not in mods
- mods.append(mod)
- self.trace(f"loading conftestmodule {mod!r}")
- self.consider_conftest(mod)
- return mod
-
- def _check_non_top_pytest_plugins(
- self, mod: types.ModuleType, conftestpath: py.path.local,
- ) -> None:
- if (
- hasattr(mod, "pytest_plugins")
- and self._configured
- and not self._using_pyargs
- ):
- msg = (
- "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
- "It affects the entire test suite instead of just below the conftest as expected.\n"
- " {}\n"
- "Please move it to a top level conftest file at the rootdir:\n"
- " {}\n"
- "For more information, visit:\n"
- " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
- )
- fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
-
+ def _importconftest(
+ self, conftestpath: py.path.local, importmode: Union[str, ImportMode],
+ ) -> types.ModuleType:
+ # Use a resolved Path object as key to avoid loading the same conftest
+ # twice with build systems that create build directories containing
+ # symlinks to actual files.
+ # Using Path().resolve() is better than py.path.realpath because
+ # it resolves to the correct path/drive in case-insensitive file systems (#5792)
+ key = Path(str(conftestpath)).resolve()
+
+ with contextlib.suppress(KeyError):
+ return self._conftestpath2mod[key]
+
+ pkgpath = conftestpath.pypkgpath()
+ if pkgpath is None:
+ _ensure_removed_sysmodule(conftestpath.purebasename)
+
+ try:
+ mod = import_path(conftestpath, mode=importmode)
+ except Exception as e:
+ assert e.__traceback__ is not None
+ exc_info = (type(e), e, e.__traceback__)
+ raise ConftestImportFailure(conftestpath, exc_info) from e
+
+ self._check_non_top_pytest_plugins(mod, conftestpath)
+
+ self._conftest_plugins.add(mod)
+ self._conftestpath2mod[key] = mod
+ dirpath = conftestpath.dirpath()
+ if dirpath in self._dirpath2confmods:
+ for path, mods in self._dirpath2confmods.items():
+ if path and path.relto(dirpath) or path == dirpath:
+ assert mod not in mods
+ mods.append(mod)
+ self.trace(f"loading conftestmodule {mod!r}")
+ self.consider_conftest(mod)
+ return mod
+
+ def _check_non_top_pytest_plugins(
+ self, mod: types.ModuleType, conftestpath: py.path.local,
+ ) -> None:
+ if (
+ hasattr(mod, "pytest_plugins")
+ and self._configured
+ and not self._using_pyargs
+ ):
+ msg = (
+ "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
+ "It affects the entire test suite instead of just below the conftest as expected.\n"
+ " {}\n"
+ "Please move it to a top level conftest file at the rootdir:\n"
+ " {}\n"
+ "For more information, visit:\n"
+ " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
+ )
+ fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
+
#
# API for bootstrapping plugin loading
#
#
- def consider_preparse(
- self, args: Sequence[str], *, exclude_only: bool = False
- ) -> None:
- i = 0
- n = len(args)
- while i < n:
- opt = args[i]
- i += 1
- if isinstance(opt, str):
- if opt == "-p":
- try:
- parg = args[i]
- except IndexError:
- return
- i += 1
- elif opt.startswith("-p"):
- parg = opt[2:]
- else:
- continue
- if exclude_only and not parg.startswith("no:"):
- continue
- self.consider_pluginarg(parg)
-
- def consider_pluginarg(self, arg: str) -> None:
+ def consider_preparse(
+ self, args: Sequence[str], *, exclude_only: bool = False
+ ) -> None:
+ i = 0
+ n = len(args)
+ while i < n:
+ opt = args[i]
+ i += 1
+ if isinstance(opt, str):
+ if opt == "-p":
+ try:
+ parg = args[i]
+ except IndexError:
+ return
+ i += 1
+ elif opt.startswith("-p"):
+ parg = opt[2:]
+ else:
+ continue
+ if exclude_only and not parg.startswith("no:"):
+ continue
+ self.consider_pluginarg(parg)
+
+ def consider_pluginarg(self, arg: str) -> None:
if arg.startswith("no:"):
name = arg[3:]
- if name in essential_plugins:
- raise UsageError("plugin %s cannot be disabled" % name)
-
- # PR #4304: remove stepwise if cacheprovider is blocked.
+ if name in essential_plugins:
+ raise UsageError("plugin %s cannot be disabled" % name)
+
+ # PR #4304: remove stepwise if cacheprovider is blocked.
if name == "cacheprovider":
self.set_blocked("stepwise")
self.set_blocked("pytest_stepwise")
@@ -649,100 +649,100 @@ class PytestPluginManager(PluginManager):
if not name.startswith("pytest_"):
self.set_blocked("pytest_" + name)
else:
- name = arg
- # Unblock the plugin. None indicates that it has been blocked.
- # There is no interface with pluggy for this.
- if self._name2plugin.get(name, -1) is None:
- del self._name2plugin[name]
- if not name.startswith("pytest_"):
- if self._name2plugin.get("pytest_" + name, -1) is None:
- del self._name2plugin["pytest_" + name]
- self.import_plugin(arg, consider_entry_points=True)
-
- def consider_conftest(self, conftestmodule: types.ModuleType) -> None:
+ name = arg
+ # Unblock the plugin. None indicates that it has been blocked.
+ # There is no interface with pluggy for this.
+ if self._name2plugin.get(name, -1) is None:
+ del self._name2plugin[name]
+ if not name.startswith("pytest_"):
+ if self._name2plugin.get("pytest_" + name, -1) is None:
+ del self._name2plugin["pytest_" + name]
+ self.import_plugin(arg, consider_entry_points=True)
+
+ def consider_conftest(self, conftestmodule: types.ModuleType) -> None:
self.register(conftestmodule, name=conftestmodule.__file__)
- def consider_env(self) -> None:
+ def consider_env(self) -> None:
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
- def consider_module(self, mod: types.ModuleType) -> None:
+ def consider_module(self, mod: types.ModuleType) -> None:
self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
- def _import_plugin_specs(
- self, spec: Union[None, types.ModuleType, str, Sequence[str]]
- ) -> None:
+ def _import_plugin_specs(
+ self, spec: Union[None, types.ModuleType, str, Sequence[str]]
+ ) -> None:
plugins = _get_plugin_specs_as_list(spec)
for import_spec in plugins:
self.import_plugin(import_spec)
- def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
- """Import a plugin with ``modname``.
-
- If ``consider_entry_points`` is True, entry point names are also
- considered to find a plugin.
- """
- # Most often modname refers to builtin modules, e.g. "pytester",
+ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
+ """Import a plugin with ``modname``.
+
+ If ``consider_entry_points`` is True, entry point names are also
+ considered to find a plugin.
+ """
+ # Most often modname refers to builtin modules, e.g. "pytester",
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
- assert isinstance(modname, str), (
+ assert isinstance(modname, str), (
"module name as text required, got %r" % modname
)
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
return
-
- importspec = "_pytest." + modname if modname in builtin_plugins else modname
+
+ importspec = "_pytest." + modname if modname in builtin_plugins else modname
self.rewrite_hook.mark_rewrite(importspec)
-
- if consider_entry_points:
- loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
- if loaded:
- return
-
+
+ if consider_entry_points:
+ loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
+ if loaded:
+ return
+
try:
__import__(importspec)
except ImportError as e:
- raise ImportError(
- 'Error importing plugin "{}": {}'.format(modname, str(e.args[0]))
- ).with_traceback(e.__traceback__) from e
+ raise ImportError(
+ 'Error importing plugin "{}": {}'.format(modname, str(e.args[0]))
+ ).with_traceback(e.__traceback__) from e
except Skipped as e:
- self.skipped_plugins.append((modname, e.msg or ""))
+ self.skipped_plugins.append((modname, e.msg or ""))
else:
mod = sys.modules[importspec]
self.register(mod, modname)
-def _get_plugin_specs_as_list(
- specs: Union[None, types.ModuleType, str, Sequence[str]]
-) -> List[str]:
- """Parse a plugins specification into a list of plugin names."""
- # None means empty.
- if specs is None:
- return []
- # Workaround for #3899 - a submodule which happens to be called "pytest_plugins".
- if isinstance(specs, types.ModuleType):
- return []
- # Comma-separated list.
- if isinstance(specs, str):
- return specs.split(",") if specs else []
- # Direct specification.
- if isinstance(specs, collections.abc.Sequence):
+def _get_plugin_specs_as_list(
+ specs: Union[None, types.ModuleType, str, Sequence[str]]
+) -> List[str]:
+ """Parse a plugins specification into a list of plugin names."""
+ # None means empty.
+ if specs is None:
+ return []
+ # Workaround for #3899 - a submodule which happens to be called "pytest_plugins".
+ if isinstance(specs, types.ModuleType):
+ return []
+ # Comma-separated list.
+ if isinstance(specs, str):
+ return specs.split(",") if specs else []
+ # Direct specification.
+ if isinstance(specs, collections.abc.Sequence):
return list(specs)
- raise UsageError(
- "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r"
- % specs
- )
+ raise UsageError(
+ "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r"
+ % specs
+ )
-def _ensure_removed_sysmodule(modname: str) -> None:
+def _ensure_removed_sysmodule(modname: str) -> None:
try:
del sys.modules[modname]
except KeyError:
pass
-class Notset:
+class Notset:
def __repr__(self):
return "<NOTSET>"
@@ -750,238 +750,238 @@ class Notset:
notset = Notset()
-def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
- """Given an iterable of file names in a source distribution, return the "names" that should
- be marked for assertion rewrite.
-
- For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in
- the assertion rewrite mechanism.
-
- This function has to deal with dist-info based distributions and egg based distributions
- (which are still very much in use for "editable" installs).
-
- Here are the file names as seen in a dist-info based distribution:
-
- pytest_mock/__init__.py
- pytest_mock/_version.py
- pytest_mock/plugin.py
- pytest_mock.egg-info/PKG-INFO
-
- Here are the file names as seen in an egg based distribution:
-
- src/pytest_mock/__init__.py
- src/pytest_mock/_version.py
- src/pytest_mock/plugin.py
- src/pytest_mock.egg-info/PKG-INFO
- LICENSE
- setup.py
-
- We have to take in account those two distribution flavors in order to determine which
- names should be considered for assertion rewriting.
-
- More information:
- https://github.com/pytest-dev/pytest-mock/issues/167
- """
- package_files = list(package_files)
- seen_some = False
+def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
+ """Given an iterable of file names in a source distribution, return the "names" that should
+ be marked for assertion rewrite.
+
+ For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in
+ the assertion rewrite mechanism.
+
+ This function has to deal with dist-info based distributions and egg based distributions
+ (which are still very much in use for "editable" installs).
+
+ Here are the file names as seen in a dist-info based distribution:
+
+ pytest_mock/__init__.py
+ pytest_mock/_version.py
+ pytest_mock/plugin.py
+ pytest_mock.egg-info/PKG-INFO
+
+ Here are the file names as seen in an egg based distribution:
+
+ src/pytest_mock/__init__.py
+ src/pytest_mock/_version.py
+ src/pytest_mock/plugin.py
+ src/pytest_mock.egg-info/PKG-INFO
+ LICENSE
+ setup.py
+
+ We have to take in account those two distribution flavors in order to determine which
+ names should be considered for assertion rewriting.
+
+ More information:
+ https://github.com/pytest-dev/pytest-mock/issues/167
+ """
+ package_files = list(package_files)
+ seen_some = False
for fn in package_files:
is_simple_module = "/" not in fn and fn.endswith(".py")
is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
if is_simple_module:
module_name, _ = os.path.splitext(fn)
- # we ignore "setup.py" at the root of the distribution
- if module_name != "setup":
- seen_some = True
- yield module_name
+ # we ignore "setup.py" at the root of the distribution
+ if module_name != "setup":
+ seen_some = True
+ yield module_name
elif is_package:
package_name = os.path.dirname(fn)
- seen_some = True
+ seen_some = True
yield package_name
- if not seen_some:
- # At this point we did not find any packages or modules suitable for assertion
- # rewriting, so we try again by stripping the first path component (to account for
- # "src" based source trees for example).
- # This approach lets us have the common case continue to be fast, as egg-distributions
- # are rarer.
- new_package_files = []
- for fn in package_files:
- parts = fn.split("/")
- new_fn = "/".join(parts[1:])
- if new_fn:
- new_package_files.append(new_fn)
- if new_package_files:
- yield from _iter_rewritable_modules(new_package_files)
-
-
-def _args_converter(args: Iterable[str]) -> Tuple[str, ...]:
- return tuple(args)
-
-
-@final
-class Config:
- """Access to configuration values, pluginmanager and plugin hooks.
-
- :param PytestPluginManager pluginmanager:
-
- :param InvocationParams invocation_params:
- Object containing parameters regarding the :func:`pytest.main`
- invocation.
- """
-
- @final
- @attr.s(frozen=True)
- class InvocationParams:
- """Holds parameters passed during :func:`pytest.main`.
-
- The object attributes are read-only.
-
- .. versionadded:: 5.1
-
- .. note::
-
- Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
- ini option are handled by pytest, not being included in the ``args`` attribute.
-
- Plugins accessing ``InvocationParams`` must be aware of that.
- """
-
- args = attr.ib(type=Tuple[str, ...], converter=_args_converter)
- """The command-line arguments as passed to :func:`pytest.main`.
-
- :type: Tuple[str, ...]
- """
- plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]])
- """Extra plugins, might be `None`.
-
- :type: Optional[Sequence[Union[str, plugin]]]
- """
- dir = attr.ib(type=Path)
- """The directory from which :func:`pytest.main` was invoked.
-
- :type: pathlib.Path
- """
-
- def __init__(
- self,
- pluginmanager: PytestPluginManager,
- *,
- invocation_params: Optional[InvocationParams] = None,
- ) -> None:
- from .argparsing import Parser, FILE_OR_DIR
-
- if invocation_params is None:
- invocation_params = self.InvocationParams(
- args=(), plugins=None, dir=Path.cwd()
- )
-
+ if not seen_some:
+ # At this point we did not find any packages or modules suitable for assertion
+ # rewriting, so we try again by stripping the first path component (to account for
+ # "src" based source trees for example).
+ # This approach lets us have the common case continue to be fast, as egg-distributions
+ # are rarer.
+ new_package_files = []
+ for fn in package_files:
+ parts = fn.split("/")
+ new_fn = "/".join(parts[1:])
+ if new_fn:
+ new_package_files.append(new_fn)
+ if new_package_files:
+ yield from _iter_rewritable_modules(new_package_files)
+
+
+def _args_converter(args: Iterable[str]) -> Tuple[str, ...]:
+ return tuple(args)
+
+
+@final
+class Config:
+ """Access to configuration values, pluginmanager and plugin hooks.
+
+ :param PytestPluginManager pluginmanager:
+
+ :param InvocationParams invocation_params:
+ Object containing parameters regarding the :func:`pytest.main`
+ invocation.
+ """
+
+ @final
+ @attr.s(frozen=True)
+ class InvocationParams:
+ """Holds parameters passed during :func:`pytest.main`.
+
+ The object attributes are read-only.
+
+ .. versionadded:: 5.1
+
+ .. note::
+
+ Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
+ ini option are handled by pytest, not being included in the ``args`` attribute.
+
+ Plugins accessing ``InvocationParams`` must be aware of that.
+ """
+
+ args = attr.ib(type=Tuple[str, ...], converter=_args_converter)
+ """The command-line arguments as passed to :func:`pytest.main`.
+
+ :type: Tuple[str, ...]
+ """
+ plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]])
+ """Extra plugins, might be `None`.
+
+ :type: Optional[Sequence[Union[str, plugin]]]
+ """
+ dir = attr.ib(type=Path)
+ """The directory from which :func:`pytest.main` was invoked.
+
+ :type: pathlib.Path
+ """
+
+ def __init__(
+ self,
+ pluginmanager: PytestPluginManager,
+ *,
+ invocation_params: Optional[InvocationParams] = None,
+ ) -> None:
+ from .argparsing import Parser, FILE_OR_DIR
+
+ if invocation_params is None:
+ invocation_params = self.InvocationParams(
+ args=(), plugins=None, dir=Path.cwd()
+ )
+
self.option = argparse.Namespace()
- """Access to command line option as attributes.
-
- :type: argparse.Namespace
- """
-
- self.invocation_params = invocation_params
- """The parameters with which pytest was invoked.
-
- :type: InvocationParams
- """
-
+ """Access to command line option as attributes.
+
+ :type: argparse.Namespace
+ """
+
+ self.invocation_params = invocation_params
+ """The parameters with which pytest was invoked.
+
+ :type: InvocationParams
+ """
+
_a = FILE_OR_DIR
self._parser = Parser(
- usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
+ usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
processopt=self._processopt,
)
self.pluginmanager = pluginmanager
- """The plugin manager handles plugin registration and hook invocation.
-
- :type: PytestPluginManager
- """
-
+ """The plugin manager handles plugin registration and hook invocation.
+
+ :type: PytestPluginManager
+ """
+
self.trace = self.pluginmanager.trace.root.get("config")
self.hook = self.pluginmanager.hook
- self._inicache: Dict[str, Any] = {}
- self._override_ini: Sequence[str] = ()
- self._opt2dest: Dict[str, str] = {}
- self._cleanup: List[Callable[[], None]] = []
- # A place where plugins can store information on the config for their
- # own use. Currently only intended for internal plugins.
- self._store = Store()
+ self._inicache: Dict[str, Any] = {}
+ self._override_ini: Sequence[str] = ()
+ self._opt2dest: Dict[str, str] = {}
+ self._cleanup: List[Callable[[], None]] = []
+ # A place where plugins can store information on the config for their
+ # own use. Currently only intended for internal plugins.
+ self._store = Store()
self.pluginmanager.register(self, "pytestconfig")
self._configured = False
- self.hook.pytest_addoption.call_historic(
- kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
- )
-
- if TYPE_CHECKING:
- from _pytest.cacheprovider import Cache
-
- self.cache: Optional[Cache] = None
-
- @property
- def invocation_dir(self) -> py.path.local:
- """The directory from which pytest was invoked.
-
- Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
- which is a :class:`pathlib.Path`.
-
- :type: py.path.local
- """
- return py.path.local(str(self.invocation_params.dir))
-
- @property
- def rootpath(self) -> Path:
- """The path to the :ref:`rootdir <rootdir>`.
-
- :type: pathlib.Path
-
- .. versionadded:: 6.1
- """
- return self._rootpath
-
- @property
- def rootdir(self) -> py.path.local:
- """The path to the :ref:`rootdir <rootdir>`.
-
- Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
-
- :type: py.path.local
- """
- return py.path.local(str(self.rootpath))
-
- @property
- def inipath(self) -> Optional[Path]:
- """The path to the :ref:`configfile <configfiles>`.
-
- :type: Optional[pathlib.Path]
-
- .. versionadded:: 6.1
- """
- return self._inipath
-
- @property
- def inifile(self) -> Optional[py.path.local]:
- """The path to the :ref:`configfile <configfiles>`.
-
- Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
-
- :type: Optional[py.path.local]
- """
- return py.path.local(str(self.inipath)) if self.inipath else None
-
- def add_cleanup(self, func: Callable[[], None]) -> None:
- """Add a function to be called when the config object gets out of
+ self.hook.pytest_addoption.call_historic(
+ kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
+ )
+
+ if TYPE_CHECKING:
+ from _pytest.cacheprovider import Cache
+
+ self.cache: Optional[Cache] = None
+
+ @property
+ def invocation_dir(self) -> py.path.local:
+ """The directory from which pytest was invoked.
+
+ Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
+ which is a :class:`pathlib.Path`.
+
+ :type: py.path.local
+ """
+ return py.path.local(str(self.invocation_params.dir))
+
+ @property
+ def rootpath(self) -> Path:
+ """The path to the :ref:`rootdir <rootdir>`.
+
+ :type: pathlib.Path
+
+ .. versionadded:: 6.1
+ """
+ return self._rootpath
+
+ @property
+ def rootdir(self) -> py.path.local:
+ """The path to the :ref:`rootdir <rootdir>`.
+
+ Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
+
+ :type: py.path.local
+ """
+ return py.path.local(str(self.rootpath))
+
+ @property
+ def inipath(self) -> Optional[Path]:
+ """The path to the :ref:`configfile <configfiles>`.
+
+ :type: Optional[pathlib.Path]
+
+ .. versionadded:: 6.1
+ """
+ return self._inipath
+
+ @property
+ def inifile(self) -> Optional[py.path.local]:
+ """The path to the :ref:`configfile <configfiles>`.
+
+ Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
+
+ :type: Optional[py.path.local]
+ """
+ return py.path.local(str(self.inipath)) if self.inipath else None
+
+ def add_cleanup(self, func: Callable[[], None]) -> None:
+ """Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
self._cleanup.append(func)
- def _do_configure(self) -> None:
+ def _do_configure(self) -> None:
assert not self._configured
self._configured = True
- with warnings.catch_warnings():
- warnings.simplefilter("default")
- self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
+ with warnings.catch_warnings():
+ warnings.simplefilter("default")
+ self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
- def _ensure_unconfigure(self) -> None:
+ def _ensure_unconfigure(self) -> None:
if self._configured:
self._configured = False
self.hook.pytest_unconfigure(config=self)
@@ -990,45 +990,45 @@ class Config:
fin = self._cleanup.pop()
fin()
- def get_terminal_writer(self) -> TerminalWriter:
- terminalreporter: TerminalReporter = self.pluginmanager.get_plugin(
- "terminalreporter"
- )
- return terminalreporter._tw
-
- def pytest_cmdline_parse(
- self, pluginmanager: PytestPluginManager, args: List[str]
- ) -> "Config":
- try:
- self.parse(args)
- except UsageError:
-
- # Handle --version and --help here in a minimal fashion.
- # This gets done via helpconfig normally, but its
- # pytest_cmdline_main is not called in case of errors.
- if getattr(self.option, "version", False) or "--version" in args:
- from _pytest.helpconfig import showversion
-
- showversion(self)
- elif (
- getattr(self.option, "help", False) or "--help" in args or "-h" in args
- ):
- self._parser._getparser().print_help()
- sys.stdout.write(
- "\nNOTE: displaying only minimal help due to UsageError.\n\n"
- )
-
- raise
+ def get_terminal_writer(self) -> TerminalWriter:
+ terminalreporter: TerminalReporter = self.pluginmanager.get_plugin(
+ "terminalreporter"
+ )
+ return terminalreporter._tw
+
+ def pytest_cmdline_parse(
+ self, pluginmanager: PytestPluginManager, args: List[str]
+ ) -> "Config":
+ try:
+ self.parse(args)
+ except UsageError:
+
+ # Handle --version and --help here in a minimal fashion.
+ # This gets done via helpconfig normally, but its
+ # pytest_cmdline_main is not called in case of errors.
+ if getattr(self.option, "version", False) or "--version" in args:
+ from _pytest.helpconfig import showversion
+
+ showversion(self)
+ elif (
+ getattr(self.option, "help", False) or "--help" in args or "-h" in args
+ ):
+ self._parser._getparser().print_help()
+ sys.stdout.write(
+ "\nNOTE: displaying only minimal help due to UsageError.\n\n"
+ )
+
+ raise
return self
- def notify_exception(
- self,
- excinfo: ExceptionInfo[BaseException],
- option: Optional[argparse.Namespace] = None,
- ) -> None:
- if option and getattr(option, "fulltrace", False):
- style: _TracebackStyle = "long"
+ def notify_exception(
+ self,
+ excinfo: ExceptionInfo[BaseException],
+ option: Optional[argparse.Namespace] = None,
+ ) -> None:
+ if option and getattr(option, "fulltrace", False):
+ style: _TracebackStyle = "long"
else:
style = "native"
excrepr = excinfo.getrepr(
@@ -1040,61 +1040,61 @@ class Config:
sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush()
- def cwd_relative_nodeid(self, nodeid: str) -> str:
- # nodeid's are relative to the rootpath, compute relative to cwd.
- if self.invocation_params.dir != self.rootpath:
- fullpath = self.rootpath / nodeid
- nodeid = bestrelpath(self.invocation_params.dir, fullpath)
+ def cwd_relative_nodeid(self, nodeid: str) -> str:
+ # nodeid's are relative to the rootpath, compute relative to cwd.
+ if self.invocation_params.dir != self.rootpath:
+ fullpath = self.rootpath / nodeid
+ nodeid = bestrelpath(self.invocation_params.dir, fullpath)
return nodeid
@classmethod
- def fromdictargs(cls, option_dict, args) -> "Config":
- """Constructor usable for subprocesses."""
- config = get_config(args)
+ def fromdictargs(cls, option_dict, args) -> "Config":
+ """Constructor usable for subprocesses."""
+ config = get_config(args)
config.option.__dict__.update(option_dict)
config.parse(args, addopts=False)
for x in config.option.plugins:
config.pluginmanager.consider_pluginarg(x)
return config
- def _processopt(self, opt: "Argument") -> None:
+ def _processopt(self, opt: "Argument") -> None:
for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest
- if hasattr(opt, "default"):
+ if hasattr(opt, "default"):
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
@hookimpl(trylast=True)
- def pytest_load_initial_conftests(self, early_config: "Config") -> None:
+ def pytest_load_initial_conftests(self, early_config: "Config") -> None:
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
- def _initini(self, args: Sequence[str]) -> None:
+ def _initini(self, args: Sequence[str]) -> None:
ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
- rootpath, inipath, inicfg = determine_setup(
+ rootpath, inipath, inicfg = determine_setup(
ns.inifilename,
ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None,
config=self,
)
- self._rootpath = rootpath
- self._inipath = inipath
- self.inicfg = inicfg
- self._parser.extra_info["rootdir"] = str(self.rootpath)
- self._parser.extra_info["inifile"] = str(self.inipath)
+ self._rootpath = rootpath
+ self._inipath = inipath
+ self.inicfg = inicfg
+ self._parser.extra_info["rootdir"] = str(self.rootpath)
+ self._parser.extra_info["inifile"] = str(self.inipath)
self._parser.addini("addopts", "extra command line options", "args")
self._parser.addini("minversion", "minimally required pytest version")
- self._parser.addini(
- "required_plugins",
- "plugins that must be present for pytest to run",
- type="args",
- default=[],
- )
+ self._parser.addini(
+ "required_plugins",
+ "plugins that must be present for pytest to run",
+ type="args",
+ default=[],
+ )
self._override_ini = ns.override_ini or ()
- def _consider_importhook(self, args: Sequence[str]) -> None:
+ def _consider_importhook(self, args: Sequence[str]) -> None:
"""Install the PEP 302 import hook if using assertion rewriting.
Needs to parse the --assert=<mode> option from the commandline
@@ -1102,22 +1102,22 @@ class Config:
by the importhook.
"""
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
- mode = getattr(ns, "assertmode", "plain")
+ mode = getattr(ns, "assertmode", "plain")
if mode == "rewrite":
- import _pytest.assertion
-
+ import _pytest.assertion
+
try:
hook = _pytest.assertion.install_importhook(self)
except SystemError:
mode = "plain"
else:
self._mark_plugins_for_rewrite(hook)
- self._warn_about_missing_assertion(mode)
+ self._warn_about_missing_assertion(mode)
- def _mark_plugins_for_rewrite(self, hook) -> None:
- """Given an importhook, mark for rewrite any top-level
+ def _mark_plugins_for_rewrite(self, hook) -> None:
+ """Given an importhook, mark for rewrite any top-level
modules or packages in the distribution package for
- all pytest plugins."""
+ all pytest plugins."""
self.pluginmanager.rewrite_hook = hook
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
@@ -1125,155 +1125,155 @@ class Config:
return
package_files = (
- str(file)
- for dist in importlib_metadata.distributions()
- if any(ep.group == "pytest11" for ep in dist.entry_points)
- for file in dist.files or []
+ str(file)
+ for dist in importlib_metadata.distributions()
+ if any(ep.group == "pytest11" for ep in dist.entry_points)
+ for file in dist.files or []
)
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
- def _validate_args(self, args: List[str], via: str) -> List[str]:
- """Validate known args."""
- self._parser._config_source_hint = via # type: ignore
- try:
- self._parser.parse_known_and_unknown_args(
- args, namespace=copy.copy(self.option)
- )
- finally:
- del self._parser._config_source_hint # type: ignore
-
- return args
-
- def _preparse(self, args: List[str], addopts: bool = True) -> None:
+ def _validate_args(self, args: List[str], via: str) -> List[str]:
+ """Validate known args."""
+ self._parser._config_source_hint = via # type: ignore
+ try:
+ self._parser.parse_known_and_unknown_args(
+ args, namespace=copy.copy(self.option)
+ )
+ finally:
+ del self._parser._config_source_hint # type: ignore
+
+ return args
+
+ def _preparse(self, args: List[str], addopts: bool = True) -> None:
if addopts:
- env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
- if len(env_addopts):
- args[:] = (
- self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
- + args
- )
+ env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
+ if len(env_addopts):
+ args[:] = (
+ self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
+ + args
+ )
self._initini(args)
if addopts:
- args[:] = (
- self._validate_args(self.getini("addopts"), "via addopts config") + args
- )
-
- self.known_args_namespace = self._parser.parse_known_args(
- args, namespace=copy.copy(self.option)
- )
+ args[:] = (
+ self._validate_args(self.getini("addopts"), "via addopts config") + args
+ )
+
+ self.known_args_namespace = self._parser.parse_known_args(
+ args, namespace=copy.copy(self.option)
+ )
self._checkversion()
self._consider_importhook(args)
- self.pluginmanager.consider_preparse(args, exclude_only=False)
+ self.pluginmanager.consider_preparse(args, exclude_only=False)
if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
# Don't autoload from setuptools entry point. Only explicitly specified
# plugins are going to be loaded.
self.pluginmanager.load_setuptools_entrypoints("pytest11")
self.pluginmanager.consider_env()
-
- self.known_args_namespace = self._parser.parse_known_args(
- args, namespace=copy.copy(self.known_args_namespace)
+
+ self.known_args_namespace = self._parser.parse_known_args(
+ args, namespace=copy.copy(self.known_args_namespace)
)
-
- self._validate_plugins()
- self._warn_about_skipped_plugins()
-
- if self.known_args_namespace.strict:
- self.issue_config_time_warning(
- _pytest.deprecated.STRICT_OPTION, stacklevel=2
- )
-
- if self.known_args_namespace.confcutdir is None and self.inipath is not None:
- confcutdir = str(self.inipath.parent)
+
+ self._validate_plugins()
+ self._warn_about_skipped_plugins()
+
+ if self.known_args_namespace.strict:
+ self.issue_config_time_warning(
+ _pytest.deprecated.STRICT_OPTION, stacklevel=2
+ )
+
+ if self.known_args_namespace.confcutdir is None and self.inipath is not None:
+ confcutdir = str(self.inipath.parent)
self.known_args_namespace.confcutdir = confcutdir
try:
self.hook.pytest_load_initial_conftests(
early_config=self, args=args, parser=self._parser
)
- except ConftestImportFailure as e:
- if self.known_args_namespace.help or self.known_args_namespace.version:
+ except ConftestImportFailure as e:
+ if self.known_args_namespace.help or self.known_args_namespace.version:
# we don't want to prevent --help/--version to work
# so just let is pass and print a warning at the end
- self.issue_config_time_warning(
- PytestConfigWarning(f"could not load initial conftests: {e.path}"),
- stacklevel=2,
- )
+ self.issue_config_time_warning(
+ PytestConfigWarning(f"could not load initial conftests: {e.path}"),
+ stacklevel=2,
+ )
else:
raise
- @hookimpl(hookwrapper=True)
- def pytest_collection(self) -> Generator[None, None, None]:
- """Validate invalid ini keys after collection is done so we take in account
- options added by late-loading conftest files."""
- yield
- self._validate_config_options()
-
- def _checkversion(self) -> None:
+ @hookimpl(hookwrapper=True)
+ def pytest_collection(self) -> Generator[None, None, None]:
+ """Validate invalid ini keys after collection is done so we take in account
+ options added by late-loading conftest files."""
+ yield
+ self._validate_config_options()
+
+ def _checkversion(self) -> None:
import pytest
minver = self.inicfg.get("minversion", None)
if minver:
- # Imported lazily to improve start-up time.
- from packaging.version import Version
-
- if not isinstance(minver, str):
- raise pytest.UsageError(
- "%s: 'minversion' must be a single value" % self.inipath
- )
-
- if Version(minver) > Version(pytest.__version__):
+ # Imported lazily to improve start-up time.
+ from packaging.version import Version
+
+ if not isinstance(minver, str):
+ raise pytest.UsageError(
+ "%s: 'minversion' must be a single value" % self.inipath
+ )
+
+ if Version(minver) > Version(pytest.__version__):
raise pytest.UsageError(
- "%s: 'minversion' requires pytest-%s, actual pytest-%s'"
- % (self.inipath, minver, pytest.__version__,)
+ "%s: 'minversion' requires pytest-%s, actual pytest-%s'"
+ % (self.inipath, minver, pytest.__version__,)
)
- def _validate_config_options(self) -> None:
- for key in sorted(self._get_unknown_ini_keys()):
- self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
-
- def _validate_plugins(self) -> None:
- required_plugins = sorted(self.getini("required_plugins"))
- if not required_plugins:
- return
-
- # Imported lazily to improve start-up time.
- from packaging.version import Version
- from packaging.requirements import InvalidRequirement, Requirement
-
- plugin_info = self.pluginmanager.list_plugin_distinfo()
- plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
-
- missing_plugins = []
- for required_plugin in required_plugins:
- try:
- spec = Requirement(required_plugin)
- except InvalidRequirement:
- missing_plugins.append(required_plugin)
- continue
-
- if spec.name not in plugin_dist_info:
- missing_plugins.append(required_plugin)
- elif Version(plugin_dist_info[spec.name]) not in spec.specifier:
- missing_plugins.append(required_plugin)
-
- if missing_plugins:
- raise UsageError(
- "Missing required plugins: {}".format(", ".join(missing_plugins)),
- )
-
- def _warn_or_fail_if_strict(self, message: str) -> None:
- if self.known_args_namespace.strict_config:
- raise UsageError(message)
-
- self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
-
- def _get_unknown_ini_keys(self) -> List[str]:
- parser_inicfg = self._parser._inidict
- return [name for name in self.inicfg if name not in parser_inicfg]
-
- def parse(self, args: List[str], addopts: bool = True) -> None:
- # Parse given cmdline arguments into this config object.
+ def _validate_config_options(self) -> None:
+ for key in sorted(self._get_unknown_ini_keys()):
+ self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
+
+ def _validate_plugins(self) -> None:
+ required_plugins = sorted(self.getini("required_plugins"))
+ if not required_plugins:
+ return
+
+ # Imported lazily to improve start-up time.
+ from packaging.version import Version
+ from packaging.requirements import InvalidRequirement, Requirement
+
+ plugin_info = self.pluginmanager.list_plugin_distinfo()
+ plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
+
+ missing_plugins = []
+ for required_plugin in required_plugins:
+ try:
+ spec = Requirement(required_plugin)
+ except InvalidRequirement:
+ missing_plugins.append(required_plugin)
+ continue
+
+ if spec.name not in plugin_dist_info:
+ missing_plugins.append(required_plugin)
+ elif Version(plugin_dist_info[spec.name]) not in spec.specifier:
+ missing_plugins.append(required_plugin)
+
+ if missing_plugins:
+ raise UsageError(
+ "Missing required plugins: {}".format(", ".join(missing_plugins)),
+ )
+
+ def _warn_or_fail_if_strict(self, message: str) -> None:
+ if self.known_args_namespace.strict_config:
+ raise UsageError(message)
+
+ self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
+
+ def _get_unknown_ini_keys(self) -> List[str]:
+ parser_inicfg = self._parser._inidict
+ return [name for name in self.inicfg if name not in parser_inicfg]
+
+ def parse(self, args: List[str], addopts: bool = True) -> None:
+ # Parse given cmdline arguments into this config object.
assert not hasattr(
self, "args"
), "can only parse cmdline args at most once per Config object"
@@ -1283,91 +1283,91 @@ class Config:
self._preparse(args, addopts=addopts)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
- self._parser.after_preparse = True # type: ignore
+ self._parser.after_preparse = True # type: ignore
try:
args = self._parser.parse_setoption(
args, self.option, namespace=self.option
)
if not args:
- if self.invocation_params.dir == self.rootpath:
+ if self.invocation_params.dir == self.rootpath:
args = self.getini("testpaths")
if not args:
- args = [str(self.invocation_params.dir)]
+ args = [str(self.invocation_params.dir)]
self.args = args
except PrintHelp:
pass
- def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
- """Issue and handle a warning during the "configure" stage.
-
- During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
- function because it is not possible to have hookwrappers around ``pytest_configure``.
-
- This function is mainly intended for plugins that need to issue warnings during
- ``pytest_configure`` (or similar stages).
-
- :param warning: The warning instance.
- :param stacklevel: stacklevel forwarded to warnings.warn.
- """
- if self.pluginmanager.is_blocked("warnings"):
- return
-
- cmdline_filters = self.known_args_namespace.pythonwarnings or []
- config_filters = self.getini("filterwarnings")
-
- with warnings.catch_warnings(record=True) as records:
- warnings.simplefilter("always", type(warning))
- apply_warning_filters(config_filters, cmdline_filters)
- warnings.warn(warning, stacklevel=stacklevel)
-
- if records:
- frame = sys._getframe(stacklevel - 1)
- location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
- self.hook.pytest_warning_captured.call_historic(
- kwargs=dict(
- warning_message=records[0],
- when="config",
- item=None,
- location=location,
- )
- )
- self.hook.pytest_warning_recorded.call_historic(
- kwargs=dict(
- warning_message=records[0],
- when="config",
- nodeid="",
- location=location,
- )
- )
-
- def addinivalue_line(self, name: str, line: str) -> None:
- """Add a line to an ini-file option. The option must have been
- declared but might not yet be set in which case the line becomes
- the first line in its value."""
+ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
+ """Issue and handle a warning during the "configure" stage.
+
+ During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
+ function because it is not possible to have hookwrappers around ``pytest_configure``.
+
+ This function is mainly intended for plugins that need to issue warnings during
+ ``pytest_configure`` (or similar stages).
+
+ :param warning: The warning instance.
+ :param stacklevel: stacklevel forwarded to warnings.warn.
+ """
+ if self.pluginmanager.is_blocked("warnings"):
+ return
+
+ cmdline_filters = self.known_args_namespace.pythonwarnings or []
+ config_filters = self.getini("filterwarnings")
+
+ with warnings.catch_warnings(record=True) as records:
+ warnings.simplefilter("always", type(warning))
+ apply_warning_filters(config_filters, cmdline_filters)
+ warnings.warn(warning, stacklevel=stacklevel)
+
+ if records:
+ frame = sys._getframe(stacklevel - 1)
+ location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
+ self.hook.pytest_warning_captured.call_historic(
+ kwargs=dict(
+ warning_message=records[0],
+ when="config",
+ item=None,
+ location=location,
+ )
+ )
+ self.hook.pytest_warning_recorded.call_historic(
+ kwargs=dict(
+ warning_message=records[0],
+ when="config",
+ nodeid="",
+ location=location,
+ )
+ )
+
+ def addinivalue_line(self, name: str, line: str) -> None:
+ """Add a line to an ini-file option. The option must have been
+ declared but might not yet be set in which case the line becomes
+ the first line in its value."""
x = self.getini(name)
assert isinstance(x, list)
x.append(line) # modifies the cached list inline
- def getini(self, name: str):
- """Return configuration value from an :ref:`ini file <configfiles>`.
-
- If the specified name hasn't been registered through a prior
- :py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
- call (usually from a plugin), a ValueError is raised.
- """
+ def getini(self, name: str):
+ """Return configuration value from an :ref:`ini file <configfiles>`.
+
+ If the specified name hasn't been registered through a prior
+ :py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
+ call (usually from a plugin), a ValueError is raised.
+ """
try:
return self._inicache[name]
except KeyError:
self._inicache[name] = val = self._getini(name)
return val
- def _getini(self, name: str):
+ def _getini(self, name: str):
try:
description, type, default = self._parser._inidict[name]
- except KeyError as e:
- raise ValueError(f"unknown configuration value: {name!r}") from e
- override_value = self._get_override_ini_value(name)
- if override_value is None:
+ except KeyError as e:
+ raise ValueError(f"unknown configuration value: {name!r}") from e
+ override_value = self._get_override_ini_value(name)
+ if override_value is None:
try:
value = self.inicfg[name]
except KeyError:
@@ -1376,86 +1376,86 @@ class Config:
if type is None:
return ""
return []
- else:
- value = override_value
- # Coerce the values based on types.
- #
- # Note: some coercions are only required if we are reading from .ini files, because
- # the file format doesn't contain type information, but when reading from toml we will
- # get either str or list of str values (see _parse_ini_config_from_pyproject_toml).
- # For example:
- #
- # ini:
- # a_line_list = "tests acceptance"
- # in this case, we need to split the string to obtain a list of strings.
- #
- # toml:
- # a_line_list = ["tests", "acceptance"]
- # in this case, we already have a list ready to use.
- #
+ else:
+ value = override_value
+ # Coerce the values based on types.
+ #
+ # Note: some coercions are only required if we are reading from .ini files, because
+ # the file format doesn't contain type information, but when reading from toml we will
+ # get either str or list of str values (see _parse_ini_config_from_pyproject_toml).
+ # For example:
+ #
+ # ini:
+ # a_line_list = "tests acceptance"
+ # in this case, we need to split the string to obtain a list of strings.
+ #
+ # toml:
+ # a_line_list = ["tests", "acceptance"]
+ # in this case, we already have a list ready to use.
+ #
if type == "pathlist":
- # TODO: This assert is probably not valid in all cases.
- assert self.inipath is not None
- dp = self.inipath.parent
- input_values = shlex.split(value) if isinstance(value, str) else value
- return [py.path.local(str(dp / x)) for x in input_values]
+ # TODO: This assert is probably not valid in all cases.
+ assert self.inipath is not None
+ dp = self.inipath.parent
+ input_values = shlex.split(value) if isinstance(value, str) else value
+ return [py.path.local(str(dp / x)) for x in input_values]
elif type == "args":
- return shlex.split(value) if isinstance(value, str) else value
+ return shlex.split(value) if isinstance(value, str) else value
elif type == "linelist":
- if isinstance(value, str):
- return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
- else:
- return value
+ if isinstance(value, str):
+ return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
+ else:
+ return value
elif type == "bool":
- return _strtobool(str(value).strip())
+ return _strtobool(str(value).strip())
else:
- assert type in [None, "string"]
+ assert type in [None, "string"]
return value
- def _getconftest_pathlist(
- self, name: str, path: py.path.local
- ) -> Optional[List[py.path.local]]:
+ def _getconftest_pathlist(
+ self, name: str, path: py.path.local
+ ) -> Optional[List[py.path.local]]:
try:
- mod, relroots = self.pluginmanager._rget_with_confmod(
- name, path, self.getoption("importmode")
- )
+ mod, relroots = self.pluginmanager._rget_with_confmod(
+ name, path, self.getoption("importmode")
+ )
except KeyError:
return None
modpath = py.path.local(mod.__file__).dirpath()
- values: List[py.path.local] = []
+ values: List[py.path.local] = []
for relroot in relroots:
if not isinstance(relroot, py.path.local):
- relroot = relroot.replace("/", os.sep)
+ relroot = relroot.replace("/", os.sep)
relroot = modpath.join(relroot, abs=True)
values.append(relroot)
return values
- def _get_override_ini_value(self, name: str) -> Optional[str]:
+ def _get_override_ini_value(self, name: str) -> Optional[str]:
value = None
- # override_ini is a list of "ini=value" options.
- # Always use the last item if multiple values are set for same ini-name,
- # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2.
+ # override_ini is a list of "ini=value" options.
+ # Always use the last item if multiple values are set for same ini-name,
+ # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2.
for ini_config in self._override_ini:
try:
key, user_ini_value = ini_config.split("=", 1)
- except ValueError as e:
- raise UsageError(
- "-o/--override-ini expects option=value style (got: {!r}).".format(
- ini_config
- )
- ) from e
+ except ValueError as e:
+ raise UsageError(
+ "-o/--override-ini expects option=value style (got: {!r}).".format(
+ ini_config
+ )
+ ) from e
else:
if key == name:
value = user_ini_value
return value
- def getoption(self, name: str, default=notset, skip: bool = False):
- """Return command line option value.
+ def getoption(self, name: str, default=notset, skip: bool = False):
+ """Return command line option value.
- :param name: Name of the option. You may also specify
+ :param name: Name of the option. You may also specify
the literal ``--OPT`` option instead of the "dest" option name.
- :param default: Default value if no option of that name exists.
- :param skip: If True, raise pytest.skip if option does not exists
+ :param default: Default value if no option of that name exists.
+ :param skip: If True, raise pytest.skip if option does not exists
or has a None value.
"""
name = self._opt2dest.get(name, name)
@@ -1464,143 +1464,143 @@ class Config:
if val is None and skip:
raise AttributeError(name)
return val
- except AttributeError as e:
+ except AttributeError as e:
if default is not notset:
return default
if skip:
import pytest
- pytest.skip(f"no {name!r} option found")
- raise ValueError(f"no option named {name!r}") from e
+ pytest.skip(f"no {name!r} option found")
+ raise ValueError(f"no option named {name!r}") from e
- def getvalue(self, name: str, path=None):
- """Deprecated, use getoption() instead."""
+ def getvalue(self, name: str, path=None):
+ """Deprecated, use getoption() instead."""
return self.getoption(name)
- def getvalueorskip(self, name: str, path=None):
- """Deprecated, use getoption(skip=True) instead."""
+ def getvalueorskip(self, name: str, path=None):
+ """Deprecated, use getoption(skip=True) instead."""
return self.getoption(name, skip=True)
- def _warn_about_missing_assertion(self, mode: str) -> None:
- if not _assertion_supported():
- if mode == "plain":
- warning_text = (
- "ASSERTIONS ARE NOT EXECUTED"
- " and FAILING TESTS WILL PASS. Are you"
- " using python -O?"
- )
- else:
- warning_text = (
- "assertions not in test modules or"
- " plugins will be ignored"
- " because assert statements are not executed "
- "by the underlying Python interpreter "
- "(are you using python -O?)\n"
- )
- self.issue_config_time_warning(
- PytestConfigWarning(warning_text), stacklevel=3,
- )
-
- def _warn_about_skipped_plugins(self) -> None:
- for module_name, msg in self.pluginmanager.skipped_plugins:
- self.issue_config_time_warning(
- PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"),
- stacklevel=2,
- )
-
-
-def _assertion_supported() -> bool:
+ def _warn_about_missing_assertion(self, mode: str) -> None:
+ if not _assertion_supported():
+ if mode == "plain":
+ warning_text = (
+ "ASSERTIONS ARE NOT EXECUTED"
+ " and FAILING TESTS WILL PASS. Are you"
+ " using python -O?"
+ )
+ else:
+ warning_text = (
+ "assertions not in test modules or"
+ " plugins will be ignored"
+ " because assert statements are not executed "
+ "by the underlying Python interpreter "
+ "(are you using python -O?)\n"
+ )
+ self.issue_config_time_warning(
+ PytestConfigWarning(warning_text), stacklevel=3,
+ )
+
+ def _warn_about_skipped_plugins(self) -> None:
+ for module_name, msg in self.pluginmanager.skipped_plugins:
+ self.issue_config_time_warning(
+ PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"),
+ stacklevel=2,
+ )
+
+
+def _assertion_supported() -> bool:
try:
assert False
except AssertionError:
return True
else:
- return False # type: ignore[unreachable]
+ return False # type: ignore[unreachable]
-def create_terminal_writer(
- config: Config, file: Optional[TextIO] = None
-) -> TerminalWriter:
- """Create a TerminalWriter instance configured according to the options
- in the config object.
+def create_terminal_writer(
+ config: Config, file: Optional[TextIO] = None
+) -> TerminalWriter:
+ """Create a TerminalWriter instance configured according to the options
+ in the config object.
- Every code which requires a TerminalWriter object and has access to a
- config object should use this function.
- """
- tw = TerminalWriter(file=file)
+ Every code which requires a TerminalWriter object and has access to a
+ config object should use this function.
+ """
+ tw = TerminalWriter(file=file)
if config.option.color == "yes":
tw.hasmarkup = True
- elif config.option.color == "no":
+ elif config.option.color == "no":
tw.hasmarkup = False
-
- if config.option.code_highlight == "yes":
- tw.code_highlight = True
- elif config.option.code_highlight == "no":
- tw.code_highlight = False
-
+
+ if config.option.code_highlight == "yes":
+ tw.code_highlight = True
+ elif config.option.code_highlight == "no":
+ tw.code_highlight = False
+
return tw
-def _strtobool(val: str) -> bool:
- """Convert a string representation of truth to True or False.
+def _strtobool(val: str) -> bool:
+ """Convert a string representation of truth to True or False.
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
- .. note:: Copied from distutils.util.
+ .. note:: Copied from distutils.util.
"""
val = val.lower()
if val in ("y", "yes", "t", "true", "on", "1"):
- return True
+ return True
elif val in ("n", "no", "f", "false", "off", "0"):
- return False
- else:
- raise ValueError(f"invalid truth value {val!r}")
-
-
-@lru_cache(maxsize=50)
-def parse_warning_filter(
- arg: str, *, escape: bool
-) -> Tuple[str, str, Type[Warning], str, int]:
- """Parse a warnings filter string.
-
- This is copied from warnings._setoption, but does not apply the filter,
- only parses it, and makes the escaping optional.
- """
- parts = arg.split(":")
- if len(parts) > 5:
- raise warnings._OptionError(f"too many fields (max 5): {arg!r}")
- while len(parts) < 5:
- parts.append("")
- action_, message, category_, module, lineno_ = [s.strip() for s in parts]
- action: str = warnings._getaction(action_) # type: ignore[attr-defined]
- category: Type[Warning] = warnings._getcategory(category_) # type: ignore[attr-defined]
- if message and escape:
- message = re.escape(message)
- if module and escape:
- module = re.escape(module) + r"\Z"
- if lineno_:
- try:
- lineno = int(lineno_)
- if lineno < 0:
- raise ValueError
- except (ValueError, OverflowError) as e:
- raise warnings._OptionError(f"invalid lineno {lineno_!r}") from e
+ return False
else:
- lineno = 0
- return action, message, category, module, lineno
-
-
-def apply_warning_filters(
- config_filters: Iterable[str], cmdline_filters: Iterable[str]
-) -> None:
- """Applies pytest-configured filters to the warnings module"""
- # Filters should have this precedence: cmdline options, config.
- # Filters should be applied in the inverse order of precedence.
- for arg in config_filters:
- warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
-
- for arg in cmdline_filters:
- warnings.filterwarnings(*parse_warning_filter(arg, escape=True))
+ raise ValueError(f"invalid truth value {val!r}")
+
+
+@lru_cache(maxsize=50)
+def parse_warning_filter(
+ arg: str, *, escape: bool
+) -> Tuple[str, str, Type[Warning], str, int]:
+ """Parse a warnings filter string.
+
+ This is copied from warnings._setoption, but does not apply the filter,
+ only parses it, and makes the escaping optional.
+ """
+ parts = arg.split(":")
+ if len(parts) > 5:
+ raise warnings._OptionError(f"too many fields (max 5): {arg!r}")
+ while len(parts) < 5:
+ parts.append("")
+ action_, message, category_, module, lineno_ = [s.strip() for s in parts]
+ action: str = warnings._getaction(action_) # type: ignore[attr-defined]
+ category: Type[Warning] = warnings._getcategory(category_) # type: ignore[attr-defined]
+ if message and escape:
+ message = re.escape(message)
+ if module and escape:
+ module = re.escape(module) + r"\Z"
+ if lineno_:
+ try:
+ lineno = int(lineno_)
+ if lineno < 0:
+ raise ValueError
+ except (ValueError, OverflowError) as e:
+ raise warnings._OptionError(f"invalid lineno {lineno_!r}") from e
+ else:
+ lineno = 0
+ return action, message, category, module, lineno
+
+
+def apply_warning_filters(
+ config_filters: Iterable[str], cmdline_filters: Iterable[str]
+) -> None:
+ """Applies pytest-configured filters to the warnings module"""
+ # Filters should have this precedence: cmdline options, config.
+ # Filters should be applied in the inverse order of precedence.
+ for arg in config_filters:
+ warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
+
+ for arg in cmdline_filters:
+ warnings.filterwarnings(*parse_warning_filter(arg, escape=True))
diff --git a/contrib/python/pytest/py3/_pytest/config/argparsing.py b/contrib/python/pytest/py3/_pytest/config/argparsing.py
index 9a48196552..2418831249 100644
--- a/contrib/python/pytest/py3/_pytest/config/argparsing.py
+++ b/contrib/python/pytest/py3/_pytest/config/argparsing.py
@@ -1,72 +1,72 @@
import argparse
-import sys
+import sys
import warnings
-from gettext import gettext
-from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
+from gettext import gettext
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import List
+from typing import Mapping
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import Union
import py
-import _pytest._io
-from _pytest.compat import final
-from _pytest.config.exceptions import UsageError
-
-if TYPE_CHECKING:
- from typing import NoReturn
- from typing_extensions import Literal
+import _pytest._io
+from _pytest.compat import final
+from _pytest.config.exceptions import UsageError
+if TYPE_CHECKING:
+ from typing import NoReturn
+ from typing_extensions import Literal
+
FILE_OR_DIR = "file_or_dir"
-@final
-class Parser:
- """Parser for command line arguments and ini-file values.
+@final
+class Parser:
+ """Parser for command line arguments and ini-file values.
- :ivar extra_info: Dict of generic param -> value to display in case
+ :ivar extra_info: Dict of generic param -> value to display in case
there's an error processing the command line arguments.
"""
- prog: Optional[str] = None
-
- def __init__(
- self,
- usage: Optional[str] = None,
- processopt: Optional[Callable[["Argument"], None]] = None,
- ) -> None:
+ prog: Optional[str] = None
+
+ def __init__(
+ self,
+ usage: Optional[str] = None,
+ processopt: Optional[Callable[["Argument"], None]] = None,
+ ) -> None:
self._anonymous = OptionGroup("custom options", parser=self)
- self._groups: List[OptionGroup] = []
+ self._groups: List[OptionGroup] = []
self._processopt = processopt
self._usage = usage
- self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {}
- self._ininames: List[str] = []
- self.extra_info: Dict[str, Any] = {}
+ self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {}
+ self._ininames: List[str] = []
+ self.extra_info: Dict[str, Any] = {}
- def processoption(self, option: "Argument") -> None:
+ def processoption(self, option: "Argument") -> None:
if self._processopt:
if option.dest:
self._processopt(option)
- def getgroup(
- self, name: str, description: str = "", after: Optional[str] = None
- ) -> "OptionGroup":
- """Get (or create) a named option Group.
+ def getgroup(
+ self, name: str, description: str = "", after: Optional[str] = None
+ ) -> "OptionGroup":
+ """Get (or create) a named option Group.
- :name: Name of the option group.
- :description: Long description for --help output.
- :after: Name of another group, used for ordering --help output.
+ :name: Name of the option group.
+ :description: Long description for --help output.
+ :after: Name of another group, used for ordering --help output.
The returned group object has an ``addoption`` method with the same
signature as :py:func:`parser.addoption
- <_pytest.config.argparsing.Parser.addoption>` but will be shown in the
+ <_pytest.config.argparsing.Parser.addoption>` but will be shown in the
respective group in the output of ``pytest. --help``.
"""
for group in self._groups:
@@ -80,37 +80,37 @@ class Parser:
self._groups.insert(i + 1, group)
return group
- def addoption(self, *opts: str, **attrs: Any) -> None:
- """Register a command line option.
+ def addoption(self, *opts: str, **attrs: Any) -> None:
+ """Register a command line option.
- :opts: Option names, can be short or long options.
- :attrs: Same attributes which the ``add_argument()`` function of the
- `argparse library <https://docs.python.org/library/argparse.html>`_
+ :opts: Option names, can be short or long options.
+ :attrs: Same attributes which the ``add_argument()`` function of the
+ `argparse library <https://docs.python.org/library/argparse.html>`_
accepts.
- After command line parsing, options are available on the pytest config
+ After command line parsing, options are available on the pytest config
object via ``config.option.NAME`` where ``NAME`` is usually set
by passing a ``dest`` attribute, for example
``addoption("--long", dest="NAME", ...)``.
"""
self._anonymous.addoption(*opts, **attrs)
- def parse(
- self,
- args: Sequence[Union[str, py.path.local]],
- namespace: Optional[argparse.Namespace] = None,
- ) -> argparse.Namespace:
+ def parse(
+ self,
+ args: Sequence[Union[str, py.path.local]],
+ namespace: Optional[argparse.Namespace] = None,
+ ) -> argparse.Namespace:
from _pytest._argcomplete import try_argcomplete
self.optparser = self._getparser()
try_argcomplete(self.optparser)
- strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
- return self.optparser.parse_args(strargs, namespace=namespace)
+ strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
+ return self.optparser.parse_args(strargs, namespace=namespace)
- def _getparser(self) -> "MyOptionParser":
+ def _getparser(self) -> "MyOptionParser":
from _pytest._argcomplete import filescompleter
- optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
+ optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
@@ -120,98 +120,98 @@ class Parser:
n = option.names()
a = option.attrs()
arggroup.add_argument(*n, **a)
- file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
+ file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
# bash like autocompletion for dirs (appending '/')
- # Type ignored because typeshed doesn't know about argcomplete.
- file_or_dir_arg.completer = filescompleter # type: ignore
+ # Type ignored because typeshed doesn't know about argcomplete.
+ file_or_dir_arg.completer = filescompleter # type: ignore
return optparser
- def parse_setoption(
- self,
- args: Sequence[Union[str, py.path.local]],
- option: argparse.Namespace,
- namespace: Optional[argparse.Namespace] = None,
- ) -> List[str]:
+ def parse_setoption(
+ self,
+ args: Sequence[Union[str, py.path.local]],
+ option: argparse.Namespace,
+ namespace: Optional[argparse.Namespace] = None,
+ ) -> List[str]:
parsedoption = self.parse(args, namespace=namespace)
for name, value in parsedoption.__dict__.items():
setattr(option, name, value)
- return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
-
- def parse_known_args(
- self,
- args: Sequence[Union[str, py.path.local]],
- namespace: Optional[argparse.Namespace] = None,
- ) -> argparse.Namespace:
- """Parse and return a namespace object with known arguments at this point."""
+ return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
+
+ def parse_known_args(
+ self,
+ args: Sequence[Union[str, py.path.local]],
+ namespace: Optional[argparse.Namespace] = None,
+ ) -> argparse.Namespace:
+ """Parse and return a namespace object with known arguments at this point."""
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
- def parse_known_and_unknown_args(
- self,
- args: Sequence[Union[str, py.path.local]],
- namespace: Optional[argparse.Namespace] = None,
- ) -> Tuple[argparse.Namespace, List[str]]:
- """Parse and return a namespace object with known arguments, and
- the remaining arguments unknown at this point."""
+ def parse_known_and_unknown_args(
+ self,
+ args: Sequence[Union[str, py.path.local]],
+ namespace: Optional[argparse.Namespace] = None,
+ ) -> Tuple[argparse.Namespace, List[str]]:
+ """Parse and return a namespace object with known arguments, and
+ the remaining arguments unknown at this point."""
optparser = self._getparser()
- strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
- return optparser.parse_known_args(strargs, namespace=namespace)
-
- def addini(
- self,
- name: str,
- help: str,
- type: Optional[
- "Literal['string', 'pathlist', 'args', 'linelist', 'bool']"
- ] = None,
- default=None,
- ) -> None:
- """Register an ini-file option.
-
- :name: Name of the ini-variable.
- :type: Type of the variable, can be ``string``, ``pathlist``, ``args``,
- ``linelist`` or ``bool``. Defaults to ``string`` if ``None`` or
- not passed.
- :default: Default value if no ini-file option exists but is queried.
+ strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
+ return optparser.parse_known_args(strargs, namespace=namespace)
+
+ def addini(
+ self,
+ name: str,
+ help: str,
+ type: Optional[
+ "Literal['string', 'pathlist', 'args', 'linelist', 'bool']"
+ ] = None,
+ default=None,
+ ) -> None:
+ """Register an ini-file option.
+
+ :name: Name of the ini-variable.
+ :type: Type of the variable, can be ``string``, ``pathlist``, ``args``,
+ ``linelist`` or ``bool``. Defaults to ``string`` if ``None`` or
+ not passed.
+ :default: Default value if no ini-file option exists but is queried.
The value of ini-variables can be retrieved via a call to
:py:func:`config.getini(name) <_pytest.config.Config.getini>`.
"""
- assert type in (None, "string", "pathlist", "args", "linelist", "bool")
+ assert type in (None, "string", "pathlist", "args", "linelist", "bool")
self._inidict[name] = (help, type, default)
self._ininames.append(name)
class ArgumentError(Exception):
- """Raised if an Argument instance is created with invalid or
- inconsistent arguments."""
+ """Raised if an Argument instance is created with invalid or
+ inconsistent arguments."""
- def __init__(self, msg: str, option: Union["Argument", str]) -> None:
+ def __init__(self, msg: str, option: Union["Argument", str]) -> None:
self.msg = msg
self.option_id = str(option)
- def __str__(self) -> str:
+ def __str__(self) -> str:
if self.option_id:
- return f"option {self.option_id}: {self.msg}"
+ return f"option {self.option_id}: {self.msg}"
else:
return self.msg
-class Argument:
- """Class that mimics the necessary behaviour of optparse.Option.
-
- It's currently a least effort implementation and ignoring choices
- and integer prefixes.
+class Argument:
+ """Class that mimics the necessary behaviour of optparse.Option.
+ It's currently a least effort implementation and ignoring choices
+ and integer prefixes.
+
https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
"""
_typ_map = {"int": int, "string": str, "float": float, "complex": complex}
- def __init__(self, *names: str, **attrs: Any) -> None:
- """Store parms in private vars for use in add_argument."""
+ def __init__(self, *names: str, **attrs: Any) -> None:
+ """Store parms in private vars for use in add_argument."""
self._attrs = attrs
- self._short_opts: List[str] = []
- self._long_opts: List[str] = []
+ self._short_opts: List[str] = []
+ self._long_opts: List[str] = []
if "%default" in (attrs.get("help") or ""):
warnings.warn(
'pytest now uses argparse. "%default" should be'
@@ -224,8 +224,8 @@ class Argument:
except KeyError:
pass
else:
- # This might raise a keyerror as well, don't want to catch that.
- if isinstance(typ, str):
+ # This might raise a keyerror as well, don't want to catch that.
+ if isinstance(typ, str):
if typ == "choice":
warnings.warn(
"`type` argument to addoption() is the string %r."
@@ -247,35 +247,35 @@ class Argument:
stacklevel=4,
)
attrs["type"] = Argument._typ_map[typ]
- # Used in test_parseopt -> test_parse_defaultgetter.
+ # Used in test_parseopt -> test_parse_defaultgetter.
self.type = attrs["type"]
else:
self.type = typ
try:
- # Attribute existence is tested in Config._processopt.
+ # Attribute existence is tested in Config._processopt.
self.default = attrs["default"]
except KeyError:
pass
self._set_opt_strings(names)
- dest: Optional[str] = attrs.get("dest")
- if dest:
- self.dest = dest
- elif self._long_opts:
- self.dest = self._long_opts[0][2:].replace("-", "_")
- else:
- try:
- self.dest = self._short_opts[0][1:]
- except IndexError as e:
- self.dest = "???" # Needed for the error repr.
- raise ArgumentError("need a long or short option", self) from e
-
- def names(self) -> List[str]:
+ dest: Optional[str] = attrs.get("dest")
+ if dest:
+ self.dest = dest
+ elif self._long_opts:
+ self.dest = self._long_opts[0][2:].replace("-", "_")
+ else:
+ try:
+ self.dest = self._short_opts[0][1:]
+ except IndexError as e:
+ self.dest = "???" # Needed for the error repr.
+ raise ArgumentError("need a long or short option", self) from e
+
+ def names(self) -> List[str]:
return self._short_opts + self._long_opts
- def attrs(self) -> Mapping[str, Any]:
- # Update any attributes set by processopt.
+ def attrs(self) -> Mapping[str, Any]:
+ # Update any attributes set by processopt.
attrs = "default dest help".split()
- attrs.append(self.dest)
+ attrs.append(self.dest)
for attr in attrs:
try:
self._attrs[attr] = getattr(self, attr)
@@ -288,11 +288,11 @@ class Argument:
self._attrs["help"] = a
return self._attrs
- def _set_opt_strings(self, opts: Sequence[str]) -> None:
- """Directly from optparse.
+ def _set_opt_strings(self, opts: Sequence[str]) -> None:
+ """Directly from optparse.
- Might not be necessary as this is passed to argparse later on.
- """
+ Might not be necessary as this is passed to argparse later on.
+ """
for opt in opts:
if len(opt) < 2:
raise ArgumentError(
@@ -317,8 +317,8 @@ class Argument:
)
self._long_opts.append(opt)
- def __repr__(self) -> str:
- args: List[str] = []
+ def __repr__(self) -> str:
+ args: List[str] = []
if self._short_opts:
args += ["_short_opts: " + repr(self._short_opts)]
if self._long_opts:
@@ -331,22 +331,22 @@ class Argument:
return "Argument({})".format(", ".join(args))
-class OptionGroup:
- def __init__(
- self, name: str, description: str = "", parser: Optional[Parser] = None
- ) -> None:
+class OptionGroup:
+ def __init__(
+ self, name: str, description: str = "", parser: Optional[Parser] = None
+ ) -> None:
self.name = name
self.description = description
- self.options: List[Argument] = []
+ self.options: List[Argument] = []
self.parser = parser
- def addoption(self, *optnames: str, **attrs: Any) -> None:
- """Add an option to this group.
+ def addoption(self, *optnames: str, **attrs: Any) -> None:
+ """Add an option to this group.
- If a shortened version of a long option is specified, it will
+ If a shortened version of a long option is specified, it will
be suppressed in the help. addoption('--twowords', '--two-words')
results in help showing '--two-words' only, but --twowords gets
- accepted **and** the automatic destination is in args.twowords.
+ accepted **and** the automatic destination is in args.twowords.
"""
conflict = set(optnames).intersection(
name for opt in self.options for name in opt.names()
@@ -356,11 +356,11 @@ class OptionGroup:
option = Argument(*optnames, **attrs)
self._addoption_instance(option, shortupper=False)
- def _addoption(self, *optnames: str, **attrs: Any) -> None:
+ def _addoption(self, *optnames: str, **attrs: Any) -> None:
option = Argument(*optnames, **attrs)
self._addoption_instance(option, shortupper=True)
- def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
+ def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
if not shortupper:
for opt in option._short_opts:
if opt[0] == "-" and opt[1].islower():
@@ -371,133 +371,133 @@ class OptionGroup:
class MyOptionParser(argparse.ArgumentParser):
- def __init__(
- self,
- parser: Parser,
- extra_info: Optional[Dict[str, Any]] = None,
- prog: Optional[str] = None,
- ) -> None:
+ def __init__(
+ self,
+ parser: Parser,
+ extra_info: Optional[Dict[str, Any]] = None,
+ prog: Optional[str] = None,
+ ) -> None:
self._parser = parser
argparse.ArgumentParser.__init__(
self,
- prog=prog,
+ prog=prog,
usage=parser._usage,
add_help=False,
formatter_class=DropShorterLongHelpFormatter,
- allow_abbrev=False,
+ allow_abbrev=False,
)
# extra_info is a dict of (param -> value) to display if there's
- # an usage error to provide more contextual information to the user.
- self.extra_info = extra_info if extra_info else {}
-
- def error(self, message: str) -> "NoReturn":
- """Transform argparse error message into UsageError."""
- msg = f"{self.prog}: error: {message}"
-
- if hasattr(self._parser, "_config_source_hint"):
- # Type ignored because the attribute is set dynamically.
- msg = f"{msg} ({self._parser._config_source_hint})" # type: ignore
-
- raise UsageError(self.format_usage() + msg)
-
- # Type ignored because typeshed has a very complex type in the superclass.
- def parse_args( # type: ignore
- self,
- args: Optional[Sequence[str]] = None,
- namespace: Optional[argparse.Namespace] = None,
- ) -> argparse.Namespace:
- """Allow splitting of positional arguments."""
- parsed, unrecognized = self.parse_known_args(args, namespace)
- if unrecognized:
- for arg in unrecognized:
+ # an usage error to provide more contextual information to the user.
+ self.extra_info = extra_info if extra_info else {}
+
+ def error(self, message: str) -> "NoReturn":
+ """Transform argparse error message into UsageError."""
+ msg = f"{self.prog}: error: {message}"
+
+ if hasattr(self._parser, "_config_source_hint"):
+ # Type ignored because the attribute is set dynamically.
+ msg = f"{msg} ({self._parser._config_source_hint})" # type: ignore
+
+ raise UsageError(self.format_usage() + msg)
+
+ # Type ignored because typeshed has a very complex type in the superclass.
+ def parse_args( # type: ignore
+ self,
+ args: Optional[Sequence[str]] = None,
+ namespace: Optional[argparse.Namespace] = None,
+ ) -> argparse.Namespace:
+ """Allow splitting of positional arguments."""
+ parsed, unrecognized = self.parse_known_args(args, namespace)
+ if unrecognized:
+ for arg in unrecognized:
if arg and arg[0] == "-":
- lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))]
+ lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))]
for k, v in sorted(self.extra_info.items()):
- lines.append(f" {k}: {v}")
+ lines.append(f" {k}: {v}")
self.error("\n".join(lines))
- getattr(parsed, FILE_OR_DIR).extend(unrecognized)
- return parsed
-
- if sys.version_info[:2] < (3, 9): # pragma: no cover
- # Backport of https://github.com/python/cpython/pull/14316 so we can
- # disable long --argument abbreviations without breaking short flags.
- def _parse_optional(
- self, arg_string: str
- ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
- if not arg_string:
- return None
- if not arg_string[0] in self.prefix_chars:
- return None
- if arg_string in self._option_string_actions:
- action = self._option_string_actions[arg_string]
- return action, arg_string, None
- if len(arg_string) == 1:
- return None
- if "=" in arg_string:
- option_string, explicit_arg = arg_string.split("=", 1)
- if option_string in self._option_string_actions:
- action = self._option_string_actions[option_string]
- return action, option_string, explicit_arg
- if self.allow_abbrev or not arg_string.startswith("--"):
- option_tuples = self._get_option_tuples(arg_string)
- if len(option_tuples) > 1:
- msg = gettext(
- "ambiguous option: %(option)s could match %(matches)s"
- )
- options = ", ".join(option for _, option, _ in option_tuples)
- self.error(msg % {"option": arg_string, "matches": options})
- elif len(option_tuples) == 1:
- (option_tuple,) = option_tuples
- return option_tuple
- if self._negative_number_matcher.match(arg_string):
- if not self._has_negative_number_optionals:
- return None
- if " " in arg_string:
- return None
- return None, arg_string, None
-
-
+ getattr(parsed, FILE_OR_DIR).extend(unrecognized)
+ return parsed
+
+ if sys.version_info[:2] < (3, 9): # pragma: no cover
+ # Backport of https://github.com/python/cpython/pull/14316 so we can
+ # disable long --argument abbreviations without breaking short flags.
+ def _parse_optional(
+ self, arg_string: str
+ ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
+ if not arg_string:
+ return None
+ if not arg_string[0] in self.prefix_chars:
+ return None
+ if arg_string in self._option_string_actions:
+ action = self._option_string_actions[arg_string]
+ return action, arg_string, None
+ if len(arg_string) == 1:
+ return None
+ if "=" in arg_string:
+ option_string, explicit_arg = arg_string.split("=", 1)
+ if option_string in self._option_string_actions:
+ action = self._option_string_actions[option_string]
+ return action, option_string, explicit_arg
+ if self.allow_abbrev or not arg_string.startswith("--"):
+ option_tuples = self._get_option_tuples(arg_string)
+ if len(option_tuples) > 1:
+ msg = gettext(
+ "ambiguous option: %(option)s could match %(matches)s"
+ )
+ options = ", ".join(option for _, option, _ in option_tuples)
+ self.error(msg % {"option": arg_string, "matches": options})
+ elif len(option_tuples) == 1:
+ (option_tuple,) = option_tuples
+ return option_tuple
+ if self._negative_number_matcher.match(arg_string):
+ if not self._has_negative_number_optionals:
+ return None
+ if " " in arg_string:
+ return None
+ return None, arg_string, None
+
+
class DropShorterLongHelpFormatter(argparse.HelpFormatter):
- """Shorten help for long options that differ only in extra hyphens.
+ """Shorten help for long options that differ only in extra hyphens.
- - Collapse **long** options that are the same except for extra hyphens.
- - Shortcut if there are only two options and one of them is a short one.
- - Cache result on the action object as this is called at least 2 times.
+ - Collapse **long** options that are the same except for extra hyphens.
+ - Shortcut if there are only two options and one of them is a short one.
+ - Cache result on the action object as this is called at least 2 times.
"""
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- # Use more accurate terminal width.
- if "width" not in kwargs:
- kwargs["width"] = _pytest._io.get_terminal_width()
- super().__init__(*args, **kwargs)
-
- def _format_action_invocation(self, action: argparse.Action) -> str:
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ # Use more accurate terminal width.
+ if "width" not in kwargs:
+ kwargs["width"] = _pytest._io.get_terminal_width()
+ super().__init__(*args, **kwargs)
+
+ def _format_action_invocation(self, action: argparse.Action) -> str:
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
if orgstr and orgstr[0] != "-": # only optional arguments
return orgstr
- res: Optional[str] = getattr(action, "_formatted_action_invocation", None)
+ res: Optional[str] = getattr(action, "_formatted_action_invocation", None)
if res:
return res
options = orgstr.split(", ")
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
# a shortcut for '-h, --help' or '--abc', '-a'
- action._formatted_action_invocation = orgstr # type: ignore
+ action._formatted_action_invocation = orgstr # type: ignore
return orgstr
return_list = []
- short_long: Dict[str, str] = {}
+ short_long: Dict[str, str] = {}
for option in options:
if len(option) == 2 or option[2] == " ":
continue
if not option.startswith("--"):
raise ArgumentError(
- 'long optional argument without "--": [%s]' % (option), option
+ 'long optional argument without "--": [%s]' % (option), option
)
xxoption = option[2:]
- shortened = xxoption.replace("-", "")
- if shortened not in short_long or len(short_long[shortened]) < len(
- xxoption
- ):
- short_long[shortened] = xxoption
+ shortened = xxoption.replace("-", "")
+ if shortened not in short_long or len(short_long[shortened]) < len(
+ xxoption
+ ):
+ short_long[shortened] = xxoption
# now short_long has been filled out to the longest with dashes
# **and** we keep the right option ordering from add_argument
for option in options:
@@ -505,18 +505,18 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return_list.append(option)
if option[2:] == short_long.get(option.replace("-", "")):
return_list.append(option.replace(" ", "=", 1))
- formatted_action_invocation = ", ".join(return_list)
- action._formatted_action_invocation = formatted_action_invocation # type: ignore
- return formatted_action_invocation
-
- def _split_lines(self, text, width):
- """Wrap lines after splitting on original newlines.
-
- This allows to have explicit line breaks in the help text.
- """
- import textwrap
-
- lines = []
- for line in text.splitlines():
- lines.extend(textwrap.wrap(line.strip(), width))
- return lines
+ formatted_action_invocation = ", ".join(return_list)
+ action._formatted_action_invocation = formatted_action_invocation # type: ignore
+ return formatted_action_invocation
+
+ def _split_lines(self, text, width):
+ """Wrap lines after splitting on original newlines.
+
+ This allows to have explicit line breaks in the help text.
+ """
+ import textwrap
+
+ lines = []
+ for line in text.splitlines():
+ lines.extend(textwrap.wrap(line.strip(), width))
+ return lines
diff --git a/contrib/python/pytest/py3/_pytest/config/exceptions.py b/contrib/python/pytest/py3/_pytest/config/exceptions.py
index 4f1320e758..ab63bfd361 100644
--- a/contrib/python/pytest/py3/_pytest/config/exceptions.py
+++ b/contrib/python/pytest/py3/_pytest/config/exceptions.py
@@ -1,11 +1,11 @@
-from _pytest.compat import final
-
-
-@final
+from _pytest.compat import final
+
+
+@final
class UsageError(Exception):
- """Error in pytest usage or invocation."""
+ """Error in pytest usage or invocation."""
class PrintHelp(Exception):
- """Raised when pytest should print its help to skip the rest of the
+ """Raised when pytest should print its help to skip the rest of the
argument parsing and validation."""
diff --git a/contrib/python/pytest/py3/_pytest/config/findpaths.py b/contrib/python/pytest/py3/_pytest/config/findpaths.py
index 2edf54536b..c599736a75 100644
--- a/contrib/python/pytest/py3/_pytest/config/findpaths.py
+++ b/contrib/python/pytest/py3/_pytest/config/findpaths.py
@@ -1,211 +1,211 @@
import os
-from pathlib import Path
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-
-import iniconfig
+from pathlib import Path
+from typing import Dict
+from typing import Iterable
+from typing import List
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import Union
+
+import iniconfig
from .exceptions import UsageError
-from _pytest.outcomes import fail
-from _pytest.pathlib import absolutepath
-from _pytest.pathlib import commonpath
-
-if TYPE_CHECKING:
- from . import Config
-
-
-def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
- """Parse the given generic '.ini' file using legacy IniConfig parser, returning
- the parsed object.
-
- Raise UsageError if the file cannot be parsed.
- """
+from _pytest.outcomes import fail
+from _pytest.pathlib import absolutepath
+from _pytest.pathlib import commonpath
+
+if TYPE_CHECKING:
+ from . import Config
+
+
+def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
+ """Parse the given generic '.ini' file using legacy IniConfig parser, returning
+ the parsed object.
+
+ Raise UsageError if the file cannot be parsed.
+ """
try:
- return iniconfig.IniConfig(str(path))
- except iniconfig.ParseError as exc:
- raise UsageError(str(exc)) from exc
-
+ return iniconfig.IniConfig(str(path))
+ except iniconfig.ParseError as exc:
+ raise UsageError(str(exc)) from exc
-def load_config_dict_from_file(
- filepath: Path,
-) -> Optional[Dict[str, Union[str, List[str]]]]:
- """Load pytest configuration from the given file path, if supported.
- Return None if the file does not contain valid pytest configuration.
+def load_config_dict_from_file(
+ filepath: Path,
+) -> Optional[Dict[str, Union[str, List[str]]]]:
+ """Load pytest configuration from the given file path, if supported.
+
+ Return None if the file does not contain valid pytest configuration.
"""
- # Configuration from ini files are obtained from the [pytest] section, if present.
- if filepath.suffix == ".ini":
- iniconfig = _parse_ini_config(filepath)
-
- if "pytest" in iniconfig:
- return dict(iniconfig["pytest"].items())
- else:
- # "pytest.ini" files are always the source of configuration, even if empty.
- if filepath.name == "pytest.ini":
- return {}
-
- # '.cfg' files are considered if they contain a "[tool:pytest]" section.
- elif filepath.suffix == ".cfg":
- iniconfig = _parse_ini_config(filepath)
-
- if "tool:pytest" in iniconfig.sections:
- return dict(iniconfig["tool:pytest"].items())
- elif "pytest" in iniconfig.sections:
- # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
- # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
- fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False)
-
- # '.toml' files are considered if they contain a [tool.pytest.ini_options] table.
- elif filepath.suffix == ".toml":
- import toml
-
- config = toml.load(str(filepath))
-
- result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
- if result is not None:
- # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
- # however we need to convert all scalar values to str for compatibility with the rest
- # of the configuration system, which expects strings only.
- def make_scalar(v: object) -> Union[str, List[str]]:
- return v if isinstance(v, list) else str(v)
-
- return {k: make_scalar(v) for k, v in result.items()}
-
- return None
-
-
-def locate_config(
- args: Iterable[Path],
-) -> Tuple[
- Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]],
-]:
- """Search in the list of arguments for a valid ini-file for pytest,
- and return a tuple of (rootdir, inifile, cfg-dict)."""
- config_names = [
- "pytest.ini",
- "pyproject.toml",
- "tox.ini",
- "setup.cfg",
- ]
+ # Configuration from ini files are obtained from the [pytest] section, if present.
+ if filepath.suffix == ".ini":
+ iniconfig = _parse_ini_config(filepath)
+
+ if "pytest" in iniconfig:
+ return dict(iniconfig["pytest"].items())
+ else:
+ # "pytest.ini" files are always the source of configuration, even if empty.
+ if filepath.name == "pytest.ini":
+ return {}
+
+ # '.cfg' files are considered if they contain a "[tool:pytest]" section.
+ elif filepath.suffix == ".cfg":
+ iniconfig = _parse_ini_config(filepath)
+
+ if "tool:pytest" in iniconfig.sections:
+ return dict(iniconfig["tool:pytest"].items())
+ elif "pytest" in iniconfig.sections:
+ # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that
+ # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086).
+ fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False)
+
+ # '.toml' files are considered if they contain a [tool.pytest.ini_options] table.
+ elif filepath.suffix == ".toml":
+ import toml
+
+ config = toml.load(str(filepath))
+
+ result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
+ if result is not None:
+ # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
+ # however we need to convert all scalar values to str for compatibility with the rest
+ # of the configuration system, which expects strings only.
+ def make_scalar(v: object) -> Union[str, List[str]]:
+ return v if isinstance(v, list) else str(v)
+
+ return {k: make_scalar(v) for k, v in result.items()}
+
+ return None
+
+
+def locate_config(
+ args: Iterable[Path],
+) -> Tuple[
+ Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]],
+]:
+ """Search in the list of arguments for a valid ini-file for pytest,
+ and return a tuple of (rootdir, inifile, cfg-dict)."""
+ config_names = [
+ "pytest.ini",
+ "pyproject.toml",
+ "tox.ini",
+ "setup.cfg",
+ ]
args = [x for x in args if not str(x).startswith("-")]
if not args:
- args = [Path.cwd()]
+ args = [Path.cwd()]
for arg in args:
- argpath = absolutepath(arg)
- for base in (argpath, *argpath.parents):
- for config_name in config_names:
- p = base / config_name
- if p.is_file():
- ini_config = load_config_dict_from_file(p)
- if ini_config is not None:
- return base, p, ini_config
- return None, None, {}
-
-
-def get_common_ancestor(paths: Iterable[Path]) -> Path:
- common_ancestor: Optional[Path] = None
+ argpath = absolutepath(arg)
+ for base in (argpath, *argpath.parents):
+ for config_name in config_names:
+ p = base / config_name
+ if p.is_file():
+ ini_config = load_config_dict_from_file(p)
+ if ini_config is not None:
+ return base, p, ini_config
+ return None, None, {}
+
+
+def get_common_ancestor(paths: Iterable[Path]) -> Path:
+ common_ancestor: Optional[Path] = None
for path in paths:
if not path.exists():
continue
if common_ancestor is None:
common_ancestor = path
else:
- if common_ancestor in path.parents or path == common_ancestor:
+ if common_ancestor in path.parents or path == common_ancestor:
continue
- elif path in common_ancestor.parents:
+ elif path in common_ancestor.parents:
common_ancestor = path
else:
- shared = commonpath(path, common_ancestor)
+ shared = commonpath(path, common_ancestor)
if shared is not None:
common_ancestor = shared
if common_ancestor is None:
- common_ancestor = Path.cwd()
- elif common_ancestor.is_file():
- common_ancestor = common_ancestor.parent
+ common_ancestor = Path.cwd()
+ elif common_ancestor.is_file():
+ common_ancestor = common_ancestor.parent
return common_ancestor
-def get_dirs_from_args(args: Iterable[str]) -> List[Path]:
- def is_option(x: str) -> bool:
- return x.startswith("-")
+def get_dirs_from_args(args: Iterable[str]) -> List[Path]:
+ def is_option(x: str) -> bool:
+ return x.startswith("-")
- def get_file_part_from_node_id(x: str) -> str:
- return x.split("::")[0]
+ def get_file_part_from_node_id(x: str) -> str:
+ return x.split("::")[0]
- def get_dir_from_path(path: Path) -> Path:
- if path.is_dir():
+ def get_dir_from_path(path: Path) -> Path:
+ if path.is_dir():
return path
- return path.parent
-
- def safe_exists(path: Path) -> bool:
- # This can throw on paths that contain characters unrepresentable at the OS level,
- # or with invalid syntax on Windows (https://bugs.python.org/issue35306)
- try:
- return path.exists()
- except OSError:
- return False
-
+ return path.parent
+
+ def safe_exists(path: Path) -> bool:
+ # This can throw on paths that contain characters unrepresentable at the OS level,
+ # or with invalid syntax on Windows (https://bugs.python.org/issue35306)
+ try:
+ return path.exists()
+ except OSError:
+ return False
+
# These look like paths but may not exist
possible_paths = (
- absolutepath(get_file_part_from_node_id(arg))
+ absolutepath(get_file_part_from_node_id(arg))
for arg in args
if not is_option(arg)
)
- return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)]
-
-
-CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
+ return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)]
-def determine_setup(
- inifile: Optional[str],
- args: Sequence[str],
- rootdir_cmd_arg: Optional[str] = None,
- config: Optional["Config"] = None,
-) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
- rootdir = None
+CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
+
+
+def determine_setup(
+ inifile: Optional[str],
+ args: Sequence[str],
+ rootdir_cmd_arg: Optional[str] = None,
+ config: Optional["Config"] = None,
+) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
+ rootdir = None
dirs = get_dirs_from_args(args)
if inifile:
- inipath_ = absolutepath(inifile)
- inipath: Optional[Path] = inipath_
- inicfg = load_config_dict_from_file(inipath_) or {}
- if rootdir_cmd_arg is None:
- rootdir = get_common_ancestor(dirs)
+ inipath_ = absolutepath(inifile)
+ inipath: Optional[Path] = inipath_
+ inicfg = load_config_dict_from_file(inipath_) or {}
+ if rootdir_cmd_arg is None:
+ rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
- rootdir, inipath, inicfg = locate_config([ancestor])
- if rootdir is None and rootdir_cmd_arg is None:
- for possible_rootdir in (ancestor, *ancestor.parents):
- if (possible_rootdir / "setup.py").is_file():
- rootdir = possible_rootdir
+ rootdir, inipath, inicfg = locate_config([ancestor])
+ if rootdir is None and rootdir_cmd_arg is None:
+ for possible_rootdir in (ancestor, *ancestor.parents):
+ if (possible_rootdir / "setup.py").is_file():
+ rootdir = possible_rootdir
break
else:
- if dirs != [ancestor]:
- rootdir, inipath, inicfg = locate_config(dirs)
+ if dirs != [ancestor]:
+ rootdir, inipath, inicfg = locate_config(dirs)
if rootdir is None:
- if config is not None:
- cwd = config.invocation_params.dir
- else:
- cwd = Path.cwd()
- rootdir = get_common_ancestor([cwd, ancestor])
+ if config is not None:
+ cwd = config.invocation_params.dir
+ else:
+ cwd = Path.cwd()
+ rootdir = get_common_ancestor([cwd, ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
if is_fs_root:
rootdir = ancestor
if rootdir_cmd_arg:
- rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
- if not rootdir.is_dir():
+ rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
+ if not rootdir.is_dir():
raise UsageError(
"Directory '{}' not found. Check your '--rootdir' option.".format(
- rootdir
+ rootdir
)
)
- assert rootdir is not None
- return rootdir, inipath, inicfg or {}
+ assert rootdir is not None
+ return rootdir, inipath, inicfg or {}