diff options
| author | deshevoy <[email protected]> | 2022-02-10 16:46:57 +0300 |
|---|---|---|
| committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:46:57 +0300 |
| commit | 28148f76dbfcc644d96427d41c92f36cbf2fdc6e (patch) | |
| tree | b83306b6e37edeea782e9eed673d89286c4fef35 /contrib/python/pytest/py3/_pytest/config | |
| parent | e988f30484abe5fdeedcc7a5d3c226c01a21800c (diff) | |
Restoring authorship annotation for <[email protected]>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/config')
| -rw-r--r-- | contrib/python/pytest/py3/_pytest/config/__init__.py | 1140 | ||||
| -rw-r--r-- | contrib/python/pytest/py3/_pytest/config/argparsing.py | 534 | ||||
| -rw-r--r-- | contrib/python/pytest/py3/_pytest/config/exceptions.py | 10 | ||||
| -rw-r--r-- | contrib/python/pytest/py3/_pytest/config/findpaths.py | 114 |
4 files changed, 899 insertions, 899 deletions
diff --git a/contrib/python/pytest/py3/_pytest/config/__init__.py b/contrib/python/pytest/py3/_pytest/config/__init__.py index fb07b6bfb9e..bd9e2883f9f 100644 --- a/contrib/python/pytest/py3/_pytest/config/__init__.py +++ b/contrib/python/pytest/py3/_pytest/config/__init__.py @@ -1,16 +1,16 @@ """Command line options, ini-file and conftest.py processing.""" -import argparse +import argparse import collections.abc import contextlib -import copy +import copy import enum -import inspect -import os +import inspect +import os import re -import shlex -import sys -import types -import warnings +import shlex +import sys +import types +import warnings from functools import lru_cache from pathlib import Path from types import TracebackType @@ -30,32 +30,32 @@ 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 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 -from .findpaths import determine_setup -from _pytest._code import ExceptionInfo -from _pytest._code import filter_traceback +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.outcomes import Skipped +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 @@ -73,10 +73,10 @@ Ideally this type would be provided by pluggy itself. """ -hookimpl = HookimplMarker("pytest") -hookspec = HookspecMarker("pytest") - - +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") + + @final class ExitCode(enum.IntEnum): """Encodes the valid exit codes by pytest. @@ -100,30 +100,30 @@ class ExitCode(enum.IntEnum): NO_TESTS_COLLECTED = 5 -class ConftestImportFailure(Exception): +class ConftestImportFailure(Exception): def __init__( self, path: py.path.local, excinfo: Tuple[Type[Exception], Exception, TracebackType], ) -> None: super().__init__(path, excinfo) - self.path = path + 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) @@ -138,27 +138,27 @@ def main( :returns: An exit code. """ - try: - try: - config = _prepareconfig(args, plugins) - except ConftestImportFailure as e: - exc_info = ExceptionInfo(e.excinfo) + 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 - ) - exc_repr = ( - exc_info.getrepr(style="short", chain=False) - if exc_info.traceback - else exc_info.exconly() - ) + ) + exc_repr = ( + exc_info.getrepr(style="short", chain=False) + if exc_info.traceback + else exc_info.exconly() + ) formatted_tb = str(exc_repr) - for line in formatted_tb.splitlines(): - tw.line(line.rstrip(), red=True) + for line in formatted_tb.splitlines(): + tw.line(line.rstrip(), red=True) return ExitCode.USAGE_ERROR - else: - try: + else: + try: ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( config=config ) @@ -166,15 +166,15 @@ def main( return ExitCode(ret) except ValueError: return ret - finally: - config._ensure_unconfigure() - except UsageError as e: + finally: + config._ensure_unconfigure() + except UsageError as e: tw = TerminalWriter(sys.stderr) - for msg in e.args: + 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. @@ -194,78 +194,78 @@ def console_main() -> int: class cmdline: # compatibility namespace - main = staticmethod(main) - - + main = staticmethod(main) + + def filename_arg(path: str, optname: str) -> str: """Argparse type validator for filename arguments. - + :path: Path of filename. :optname: Name of the option. - """ - if os.path.isdir(path): + """ + if os.path.isdir(path): raise UsageError(f"{optname} must be a filename, given: {path}") - return path - - + return path + + def directory_arg(path: str, optname: str) -> str: - """Argparse type validator for directory arguments. - + """Argparse type validator for directory arguments. + :path: Path of directory. :optname: Name of the option. - """ - if not os.path.isdir(path): + """ + if not os.path.isdir(path): raise UsageError(f"{optname} must be a directory, given: {path}") - return path - - + return path + + # Plugins that cannot be disabled via "-p no:X" currently. essential_plugins = ( - "mark", - "main", - "runner", + "mark", + "main", + "runner", "fixtures", "helpconfig", # Provides -p. ) default_plugins = essential_plugins + ( - "python", + "python", "terminal", - "debugging", - "unittest", - "capture", - "skipping", - "tmpdir", - "monkeypatch", - "recwarn", - "pastebin", - "nose", - "assertion", - "junitxml", - "doctest", - "cacheprovider", - "freeze_support", - "setuponly", - "setupplan", - "stepwise", - "warnings", - "logging", + "debugging", + "unittest", + "capture", + "skipping", + "tmpdir", + "monkeypatch", + "recwarn", + "pastebin", + "nose", + "assertion", + "junitxml", + "doctest", + "cacheprovider", + "freeze_support", + "setuponly", + "setupplan", + "stepwise", + "warnings", + "logging", "reports", *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []), "faulthandler", -) - -builtin_plugins = set(default_plugins) -builtin_plugins.add("pytester") +) + +builtin_plugins = set(default_plugins) +builtin_plugins.add("pytester") builtin_plugins.add("pytester_assertions") - - + + 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() + # subsequent calls to main will create a fresh instance + pluginmanager = PytestPluginManager() config = Config( pluginmanager, invocation_params=Config.InvocationParams( @@ -277,66 +277,66 @@ def get_config( # Handle any "-p no:plugin" args. pluginmanager.consider_preparse(args, exclude_only=True) - for spec in default_plugins: - pluginmanager.import_plugin(spec) + for spec in default_plugins: + pluginmanager.import_plugin(spec) + + return config + - return config - - def get_plugin_manager() -> "PytestPluginManager": """Obtain a new instance of the - :py:class:`_pytest.config.PytestPluginManager`, with default plugins - already loaded. - - This function can be used by integration with other tools, like hooking - into pytest to run tests into an IDE. - """ - return get_config().pluginmanager - - + :py:class:`_pytest.config.PytestPluginManager`, with default plugins + already loaded. + + This function can be used by integration with other tools, like hooking + into pytest to run tests into an IDE. + """ + return get_config().pluginmanager + + 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)] + 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))) - + config = get_config(args, plugins) - pluginmanager = config.pluginmanager - try: - if plugins: - for plugin in plugins: + pluginmanager = config.pluginmanager + try: + if plugins: + for plugin in plugins: if isinstance(plugin, str): - pluginmanager.consider_pluginarg(plugin) - else: - pluginmanager.register(plugin) + pluginmanager.consider_pluginarg(plugin) + else: + pluginmanager.register(plugin) config = pluginmanager.hook.pytest_cmdline_parse( - pluginmanager=pluginmanager, args=args - ) + pluginmanager=pluginmanager, args=args + ) return config - except BaseException: - config._ensure_unconfigure() - raise - - + except BaseException: + config._ensure_unconfigure() + raise + + @final -class PytestPluginManager(PluginManager): +class PytestPluginManager(PluginManager): """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. - """ - + """ + 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() @@ -345,9 +345,9 @@ class PytestPluginManager(PluginManager): 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._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 @@ -355,71 +355,71 @@ class PytestPluginManager(PluginManager): # 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"): + 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") - try: + try: err = open( os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding, ) - except Exception: - pass - self.trace.root.setwriter(err.write) - self.enable_tracing() - - # Config._consider_importhook will set a real object if required. - self.rewrite_hook = _pytest.assertion.DummyRewriteHook() + except Exception: + pass + self.trace.root.setwriter(err.write) + self.enable_tracing() + + # 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. - self._configured = False - + self._configured = False + def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): # pytest hooks are always prefixed with "pytest_", - # so we avoid accessing possibly non-readable attributes + # so we avoid accessing possibly non-readable attributes # (see issue #1073). - if not name.startswith("pytest_"): - return + if not name.startswith("pytest_"): + return # Ignore names which can not be hooks. if name == "pytest_plugins": - return - - method = getattr(plugin, name) + return + + method = getattr(plugin, name) opts = super().parse_hookimpl_opts(plugin, name) - + # Consider only actual functions for hooks (#3775). - if not inspect.isroutine(method): - return - + if not inspect.isroutine(method): + return + # Collect unmarked hooks as long as they have the `pytest_' prefix. - if opts is None and name.startswith("pytest_"): - opts = {} + 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", [])} - - for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): + + for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): opts.setdefault(name, hasattr(method, name) or name in known_marks) - return opts - + return opts + 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 opts is None: + method = getattr(module_or_class, name) - if name.startswith("pytest_"): + 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", [])} - opts = { + opts = { "firstresult": hasattr(method, "firstresult") or "firstresult" in known_marks, "historic": hasattr(method, "historic") or "historic" in known_marks, - } - return opts - + } + return opts + def register( self, plugin: _PluggyPlugin, name: Optional[str] = None ) -> Optional[str]: @@ -430,47 +430,47 @@ class PytestPluginManager(PluginManager): "please remove it from your requirements.".format( name.replace("_", "-") ) - ) - ) + ) + ) return None ret: Optional[str] = super().register(plugin, name) - if ret: - self.hook.pytest_plugin_registered.call_historic( - kwargs=dict(plugin=plugin, manager=self) - ) - - if isinstance(plugin, types.ModuleType): - self.consider_module(plugin) - return ret - + if ret: + self.hook.pytest_plugin_registered.call_historic( + kwargs=dict(plugin=plugin, manager=self) + ) + + if isinstance(plugin, types.ModuleType): + 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 hasplugin(self, name: str) -> bool: """Return whether a plugin with the given name is registered.""" - return bool(self.get_plugin(name)) - + return bool(self.get_plugin(name)) + def pytest_configure(self, config: "Config") -> None: """:meta private:""" - # XXX now that the pluginmanager exposes hookimpl(tryfirst...) + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers. - config.addinivalue_line( - "markers", - "tryfirst: mark a hook implementation function such that the " - "plugin machinery will try to call it first/as early as possible.", - ) - config.addinivalue_line( - "markers", - "trylast: mark a hook implementation function such that the " - "plugin machinery will try to call it last/as late as possible.", - ) - self._configured = True - - # + config.addinivalue_line( + "markers", + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible.", + ) + config.addinivalue_line( + "markers", + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible.", + ) + self._configured = True + + # # Internal API for local conftest plugin handling. - # + # def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: """Load initial conftest files given a preparsed "namespace". @@ -478,77 +478,77 @@ class PytestPluginManager(PluginManager): 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 = ( - current.join(namespace.confcutdir, abs=True) - if namespace.confcutdir - else None - ) - self._noconftest = namespace.noconftest - self._using_pyargs = namespace.pyargs - testpaths = namespace.file_or_dir - foundanchor = False + """ + current = py.path.local() + self._confcutdir = ( + current.join(namespace.confcutdir, abs=True) + if namespace.confcutdir + else None + ) + self._noconftest = namespace.noconftest + self._using_pyargs = namespace.pyargs + testpaths = namespace.file_or_dir + foundanchor = False 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) + # 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) - foundanchor = True - if not foundanchor: + foundanchor = True + if not foundanchor: 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) - # let's also consider test* subdirs - if anchor.check(dir=1): - for x in anchor.listdir("test*"): - if x.check(dir=1): + # 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) - - @lru_cache(maxsize=128) + + @lru_cache(maxsize=128) def _getconftestmodules( self, path: py.path.local, importmode: Union[str, ImportMode], ) -> List[types.ModuleType]: - if self._noconftest: - return [] - - if path.isfile(): - directory = path.dirpath() - else: - directory = path - + if self._noconftest: + return [] + + if path.isfile(): + directory = path.dirpath() + else: + directory = path + # XXX these days we may rather want to use config.rootpath - # and allow users to opt into looking into the rootdir parent + # and allow users to opt into looking into the rootdir parent # directories instead of requiring to specify confcutdir. - clist = [] + clist = [] for parent in directory.parts(): - if self._confcutdir and self._confcutdir.relto(parent): - continue - conftestpath = parent.join("conftest.py") - if conftestpath.isfile(): + if self._confcutdir and self._confcutdir.relto(parent): + continue + conftestpath = parent.join("conftest.py") + if conftestpath.isfile(): mod = self._importconftest(conftestpath, importmode) - clist.append(mod) - self._dirpath2confmods[directory] = clist - return clist - + 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) - for mod in reversed(modules): - try: - return mod, getattr(mod, name) - except AttributeError: - continue - raise KeyError(name) - + for mod in reversed(modules): + try: + return mod, getattr(mod, name) + except AttributeError: + continue + raise KeyError(name) + def _importconftest( self, conftestpath: py.path.local, importmode: Union[str, ImportMode], ) -> types.ModuleType: @@ -561,11 +561,11 @@ class PytestPluginManager(PluginManager): 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: @@ -606,11 +606,11 @@ class PytestPluginManager(PluginManager): ) fail(msg.format(conftestpath, self._confcutdir), pytrace=False) - # - # API for bootstrapping plugin loading - # - # - + # + # API for bootstrapping plugin loading + # + # + def consider_preparse( self, args: Sequence[str], *, exclude_only: bool = False ) -> None: @@ -633,22 +633,22 @@ class PytestPluginManager(PluginManager): 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 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 == "cacheprovider": - self.set_blocked("stepwise") - self.set_blocked("pytest_stepwise") - - self.set_blocked(name) - if not name.startswith("pytest_"): - self.set_blocked("pytest_" + name) - else: + if name == "cacheprovider": + self.set_blocked("stepwise") + self.set_blocked("pytest_stepwise") + + self.set_blocked(name) + 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. @@ -658,23 +658,23 @@ class PytestPluginManager(PluginManager): 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__) - + self.register(conftestmodule, name=conftestmodule.__file__) + def consider_env(self) -> None: - self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) - + self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) + def consider_module(self, mod: types.ModuleType) -> None: - self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) - + self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) + 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) - + 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``. @@ -682,37 +682,37 @@ class PytestPluginManager(PluginManager): 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. + # "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), ( - "module name as text required, got %r" % modname - ) - if self.is_blocked(modname) or self.get_plugin(modname) is not None: - return + "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 - self.rewrite_hook.mark_rewrite(importspec) + self.rewrite_hook.mark_rewrite(importspec) if consider_entry_points: loaded = self.load_setuptools_entrypoints("pytest11", name=modname) if loaded: return - try: - __import__(importspec) - except ImportError as e: + try: + __import__(importspec) + except ImportError as e: raise ImportError( 'Error importing plugin "{}": {}'.format(modname, str(e.args[0])) ).with_traceback(e.__traceback__) from e - - except Skipped as e: + + except Skipped as e: self.skipped_plugins.append((modname, e.msg or "")) - else: - mod = sys.modules[importspec] - self.register(mod, modname) - - + 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]: @@ -728,28 +728,28 @@ def _get_plugin_specs_as_list( return specs.split(",") if specs else [] # Direct specification. if isinstance(specs, collections.abc.Sequence): - return list(specs) + return list(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: - try: - del sys.modules[modname] - except KeyError: - pass - - + try: + del sys.modules[modname] + except KeyError: + pass + + class Notset: - def __repr__(self): - return "<NOTSET>" - - -notset = Notset() - - + def __repr__(self): + return "<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. @@ -784,20 +784,20 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: """ 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) + 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 - elif is_package: - package_name = os.path.dirname(fn) + elif is_package: + package_name = os.path.dirname(fn) seen_some = True - yield package_name - + 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 @@ -812,11 +812,11 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: 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: @@ -875,7 +875,7 @@ class Config: args=(), plugins=None, dir=Path.cwd() ) - self.option = argparse.Namespace() + self.option = argparse.Namespace() """Access to command line option as attributes. :type: argparse.Namespace @@ -887,19 +887,19 @@ class Config: :type: InvocationParams """ - _a = FILE_OR_DIR - self._parser = Parser( + _a = FILE_OR_DIR + self._parser = Parser( usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", - processopt=self._processopt, - ) - self.pluginmanager = pluginmanager + processopt=self._processopt, + ) + self.pluginmanager = pluginmanager """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.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] = {} @@ -907,12 +907,12 @@ class Config: # 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.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 @@ -928,7 +928,7 @@ class Config: :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>`. @@ -971,44 +971,44 @@ class Config: 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) - + use (usually coninciding with pytest_unconfigure).""" + self._cleanup.append(func) + def _do_configure(self) -> None: - assert not self._configured - self._configured = True + assert not self._configured + self._configured = True with warnings.catch_warnings(): warnings.simplefilter("default") self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) - + def _ensure_unconfigure(self) -> None: - if self._configured: - self._configured = False - self.hook.pytest_unconfigure(config=self) - self.hook.pytest_configure._call_history = [] - while self._cleanup: - fin = self._cleanup.pop() - fin() - + if self._configured: + self._configured = False + self.hook.pytest_unconfigure(config=self) + self.hook.pytest_configure._call_history = [] + while self._cleanup: + 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 @@ -1017,11 +1017,11 @@ class Config: sys.stdout.write( "\nNOTE: displaying only minimal help due to UsageError.\n\n" ) - + raise - - return self - + + return self + def notify_exception( self, excinfo: ExceptionInfo[BaseException], @@ -1029,111 +1029,111 @@ class Config: ) -> None: if option and getattr(option, "fulltrace", False): style: _TracebackStyle = "long" - else: - style = "native" - excrepr = excinfo.getrepr( - funcargs=True, showlocals=getattr(option, "showlocals", False), style=style - ) - res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) - if not any(res): - for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" % line) - sys.stderr.flush() - + else: + style = "native" + excrepr = excinfo.getrepr( + funcargs=True, showlocals=getattr(option, "showlocals", False), style=style + ) + res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) + if not any(res): + for line in str(excrepr).split("\n"): + 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) - return nodeid - - @classmethod + return nodeid + + @classmethod 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 - + 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: - for name in opt._short_opts + opt._long_opts: - self._opt2dest[name] = opt.dest - + for name in opt._short_opts + opt._long_opts: + self._opt2dest[name] = opt.dest + if hasattr(opt, "default"): - if not hasattr(self.option, opt.dest): - setattr(self.option, opt.dest, opt.default) - - @hookimpl(trylast=True) + 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: - self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) - + self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) + def _initini(self, args: Sequence[str]) -> None: - ns, unknown_args = self._parser.parse_known_and_unknown_args( - args, namespace=copy.copy(self.option) - ) + ns, unknown_args = self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) rootpath, inipath, inicfg = determine_setup( - ns.inifilename, - ns.file_or_dir + unknown_args, - rootdir_cmd_arg=ns.rootdir or None, - config=self, - ) + 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._parser.addini("addopts", "extra command line options", "args") - self._parser.addini("minversion", "minimally required pytest version") + 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._override_ini = ns.override_ini or () - + self._override_ini = ns.override_ini or () + 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 - and find all the installed plugins to mark them for rewriting - by the importhook. - """ - ns, unknown_args = self._parser.parse_known_and_unknown_args(args) + """Install the PEP 302 import hook if using assertion rewriting. + + Needs to parse the --assert=<mode> option from the commandline + and find all the installed plugins to mark them for rewriting + by the importhook. + """ + ns, unknown_args = self._parser.parse_known_and_unknown_args(args) mode = getattr(ns, "assertmode", "plain") - if mode == "rewrite": + if mode == "rewrite": import _pytest.assertion - try: - hook = _pytest.assertion.install_importhook(self) - except SystemError: - mode = "plain" - else: - self._mark_plugins_for_rewrite(hook) + try: + hook = _pytest.assertion.install_importhook(self) + except SystemError: + mode = "plain" + else: + self._mark_plugins_for_rewrite(hook) self._warn_about_missing_assertion(mode) - + 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 + modules or packages in the distribution package for all pytest plugins.""" - self.pluginmanager.rewrite_hook = hook - - if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): - # We don't autoload from setuptools entry points, no need to continue. - return - - package_files = ( + self.pluginmanager.rewrite_hook = hook + + if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): + # We don't autoload from setuptools entry points, no need to continue. + 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 [] - ) - - for name in _iter_rewritable_modules(package_files): - hook.mark_rewrite(name) - + ) + + 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 @@ -1147,15 +1147,15 @@ class Config: return args def _preparse(self, args: List[str], addopts: bool = True) -> None: - if addopts: + 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 ) - self._initini(args) - if addopts: + self._initini(args) + if addopts: args[:] = ( self._validate_args(self.getini("addopts"), "via addopts config") + args ) @@ -1163,18 +1163,18 @@ class Config: self.known_args_namespace = self._parser.parse_known_args( args, namespace=copy.copy(self.option) ) - self._checkversion() - self._consider_importhook(args) + self._checkversion() + self._consider_importhook(args) 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() + 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._validate_plugins() self._warn_about_skipped_plugins() @@ -1186,22 +1186,22 @@ class Config: 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 - ) + 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: - # we don't want to prevent --help/--version to work - # so just let is pass and print a warning at the end + # 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, ) - else: - raise - + 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 @@ -1210,10 +1210,10 @@ class Config: self._validate_config_options() def _checkversion(self) -> None: - import pytest - - minver = self.inicfg.get("minversion", None) - if minver: + import pytest + + minver = self.inicfg.get("minversion", None) + if minver: # Imported lazily to improve start-up time. from packaging.version import Version @@ -1223,11 +1223,11 @@ class Config: ) if Version(minver) > Version(pytest.__version__): - raise pytest.UsageError( + raise pytest.UsageError( "%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") @@ -1274,29 +1274,29 @@ class Config: 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" - self.hook.pytest_addhooks.call_historic( - kwargs=dict(pluginmanager=self.pluginmanager) - ) - self._preparse(args, addopts=addopts) - # XXX deprecated hook: - self.hook.pytest_cmdline_preparse(config=self, args=args) + assert not hasattr( + self, "args" + ), "can only parse cmdline args at most once per Config object" + self.hook.pytest_addhooks.call_historic( + kwargs=dict(pluginmanager=self.pluginmanager) + ) + self._preparse(args, addopts=addopts) + # XXX deprecated hook: + self.hook.pytest_cmdline_preparse(config=self, args=args) self._parser.after_preparse = True # type: ignore - try: - args = self._parser.parse_setoption( - args, self.option, namespace=self.option - ) - if not args: + try: + args = self._parser.parse_setoption( + args, self.option, namespace=self.option + ) + if not args: if self.invocation_params.dir == self.rootpath: - args = self.getini("testpaths") - if not args: + args = self.getini("testpaths") + if not args: args = [str(self.invocation_params.dir)] - self.args = args - except PrintHelp: - pass - + 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. @@ -1344,10 +1344,10 @@ class Config: """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 - + 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>`. @@ -1355,27 +1355,27 @@ class Config: :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 - + try: + return self._inicache[name] + except KeyError: + self._inicache[name] = val = self._getini(name) + return val + def _getini(self, name: str): - try: - description, type, default = self._parser._inidict[name] + 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: - try: - value = self.inicfg[name] - except KeyError: - if default is not None: - return default - if type is None: - return "" - return [] + try: + value = self.inicfg[name] + except KeyError: + if default is not None: + return default + if type is None: + return "" + return [] else: value = override_value # Coerce the values based on types. @@ -1393,94 +1393,94 @@ class Config: # a_line_list = ["tests", "acceptance"] # in this case, we already have a list ready to use. # - if type == "pathlist": + 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] - elif type == "args": + elif type == "args": return shlex.split(value) if isinstance(value, str) else value - elif type == "linelist": + 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 - elif type == "bool": + elif type == "bool": return _strtobool(str(value).strip()) - else: + else: assert type in [None, "string"] - return value - + return value + def _getconftest_pathlist( self, name: str, path: py.path.local ) -> Optional[List[py.path.local]]: - try: + try: mod, relroots = self.pluginmanager._rget_with_confmod( name, path, self.getoption("importmode") ) - except KeyError: - return None - modpath = py.path.local(mod.__file__).dirpath() + except KeyError: + return None + modpath = py.path.local(mod.__file__).dirpath() values: List[py.path.local] = [] - for relroot in relroots: - if not isinstance(relroot, py.path.local): + for relroot in relroots: + if not isinstance(relroot, py.path.local): relroot = relroot.replace("/", os.sep) - relroot = modpath.join(relroot, abs=True) - values.append(relroot) - return values - + relroot = modpath.join(relroot, abs=True) + values.append(relroot) + return values + def _get_override_ini_value(self, name: str) -> Optional[str]: - value = None + 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. - for ini_config in self._override_ini: - try: - key, user_ini_value = ini_config.split("=", 1) + 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 - else: - if key == name: - value = user_ini_value - return value - + 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. - + :param name: Name of the option. You may also specify - the literal ``--OPT`` option instead of the "dest" option name. + 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 - or has a None value. - """ - name = self._opt2dest.get(name, name) - try: - val = getattr(self.option, name) - if val is None and skip: - raise AttributeError(name) - return val + or has a None value. + """ + name = self._opt2dest.get(name, name) + try: + val = getattr(self.option, name) + if val is None and skip: + raise AttributeError(name) + return val except AttributeError as e: - if default is not notset: - return default - if skip: - import pytest - + 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 - + def getvalue(self, name: str, path=None): """Deprecated, use getoption() instead.""" - return self.getoption(name) - + return self.getoption(name) + def getvalueorskip(self, name: str, path=None): """Deprecated, use getoption(skip=True) instead.""" - return self.getoption(name, skip=True) - + return self.getoption(name, skip=True) + def _warn_about_missing_assertion(self, mode: str) -> None: if not _assertion_supported(): if mode == "plain": @@ -1500,7 +1500,7 @@ class Config: 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( @@ -1510,53 +1510,53 @@ class Config: def _assertion_supported() -> bool: - try: - assert False - except AssertionError: - return True - else: + try: + assert False + except AssertionError: + return True + else: 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. - + 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 + + if config.option.color == "yes": + tw.hasmarkup = True elif config.option.color == "no": - tw.hasmarkup = False + tw.hasmarkup = False if config.option.code_highlight == "yes": tw.code_highlight = True elif config.option.code_highlight == "no": tw.code_highlight = False - return tw - - + return tw + + 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. - + + 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. - """ - val = val.lower() - if val in ("y", "yes", "t", "true", "on", "1"): + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): return True - elif val in ("n", "no", "f", "false", "off", "0"): + elif val in ("n", "no", "f", "false", "off", "0"): return False - else: + else: raise ValueError(f"invalid truth value {val!r}") diff --git a/contrib/python/pytest/py3/_pytest/config/argparsing.py b/contrib/python/pytest/py3/_pytest/config/argparsing.py index 20414f2b6e8..9a481965526 100644 --- a/contrib/python/pytest/py3/_pytest/config/argparsing.py +++ b/contrib/python/pytest/py3/_pytest/config/argparsing.py @@ -1,6 +1,6 @@ -import argparse +import argparse import sys -import warnings +import warnings from gettext import gettext from typing import Any from typing import Callable @@ -13,28 +13,28 @@ from typing import Sequence from typing import Tuple from typing import TYPE_CHECKING from typing import Union - -import py - + +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 -FILE_OR_DIR = "file_or_dir" - - +FILE_OR_DIR = "file_or_dir" + + @final class Parser: """Parser for command line arguments and ini-file values. - + :ivar extra_info: Dict of generic param -> value to display in case - there's an error processing the command line arguments. - """ - + there's an error processing the command line arguments. + """ + prog: Optional[str] = None def __init__( @@ -42,109 +42,109 @@ class Parser: usage: Optional[str] = None, processopt: Optional[Callable[["Argument"], None]] = None, ) -> None: - self._anonymous = OptionGroup("custom options", parser=self) + self._anonymous = OptionGroup("custom options", parser=self) self._groups: List[OptionGroup] = [] - self._processopt = processopt - self._usage = usage + 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] = {} - + def processoption(self, option: "Argument") -> None: - if self._processopt: - if option.dest: - self._processopt(option) - + 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. - + :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 + + 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 - respective group in the output of ``pytest. --help``. - """ - for group in self._groups: - if group.name == name: - return group - group = OptionGroup(name, description, parser=self) - i = 0 - for i, grp in enumerate(self._groups): - if grp.name == after: - break - self._groups.insert(i + 1, group) - return group - + respective group in the output of ``pytest. --help``. + """ + for group in self._groups: + if group.name == name: + return group + group = OptionGroup(name, description, parser=self) + i = 0 + for i, grp in enumerate(self._groups): + if grp.name == after: + break + self._groups.insert(i + 1, group) + return group + 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>`_ - accepts. - + accepts. + 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) - + 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: - from _pytest._argcomplete import try_argcomplete - - self.optparser = self._getparser() - try_argcomplete(self.optparser) + 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) - + def _getparser(self) -> "MyOptionParser": - from _pytest._argcomplete import filescompleter - + from _pytest._argcomplete import filescompleter + optparser = MyOptionParser(self, self.extra_info, prog=self.prog) - groups = self._groups + [self._anonymous] - for group in groups: - if group.options: - desc = group.description or group.name - arggroup = optparser.add_argument_group(desc) - for option in group.options: - n = option.names() - a = option.attrs() - arggroup.add_argument(*n, **a) + groups = self._groups + [self._anonymous] + for group in groups: + if group.options: + desc = group.description or group.name + arggroup = optparser.add_argument_group(desc) + for option in group.options: + n = option.names() + a = option.attrs() + arggroup.add_argument(*n, **a) file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*") - # bash like autocompletion for dirs (appending '/') + # bash like autocompletion for dirs (appending '/') # Type ignored because typeshed doesn't know about argcomplete. file_or_dir_arg.completer = filescompleter # type: ignore - return optparser - + return optparser + 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) + 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 self.parse_known_and_unknown_args(args, namespace=namespace)[0] - + 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]], @@ -152,10 +152,10 @@ class Parser: ) -> 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() + 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, @@ -166,97 +166,97 @@ class Parser: 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>`. - """ + + 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") - self._inidict[name] = (help, type, default) - self._ininames.append(name) - - -class ArgumentError(Exception): + 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.""" - + def __init__(self, msg: str, option: Union["Argument", str]) -> None: - self.msg = msg - self.option_id = str(option) - + self.msg = msg + self.option_id = str(option) + def __str__(self) -> str: - if self.option_id: + if self.option_id: return f"option {self.option_id}: {self.msg}" - else: - return 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. - https://docs.python.org/3/library/optparse.html#optparse-standard-option-types - """ - - _typ_map = {"int": int, "string": str, "float": float, "complex": complex} - + 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.""" - self._attrs = attrs + self._attrs = attrs 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' - ' changed to "%(default)s" ', - DeprecationWarning, - stacklevel=3, - ) - try: - typ = attrs["type"] - except KeyError: - pass - else: + if "%default" in (attrs.get("help") or ""): + warnings.warn( + 'pytest now uses argparse. "%default" should be' + ' changed to "%(default)s" ', + DeprecationWarning, + stacklevel=3, + ) + try: + typ = attrs["type"] + except KeyError: + pass + else: # 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." - " For choices this is optional and can be omitted, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: %s)" % (typ, names), - DeprecationWarning, - stacklevel=4, - ) - # argparse expects a type here take it from - # the type of the first element - attrs["type"] = type(attrs["choices"][0]) - else: - warnings.warn( - "`type` argument to addoption() is the string %r, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: %s)" % (typ, names), - DeprecationWarning, - stacklevel=4, - ) - attrs["type"] = Argument._typ_map[typ] + if typ == "choice": + warnings.warn( + "`type` argument to addoption() is the string %r." + " For choices this is optional and can be omitted, " + " but when supplied should be a type (for example `str` or `int`)." + " (options: %s)" % (typ, names), + DeprecationWarning, + stacklevel=4, + ) + # argparse expects a type here take it from + # the type of the first element + attrs["type"] = type(attrs["choices"][0]) + else: + warnings.warn( + "`type` argument to addoption() is the string %r, " + " but when supplied should be a type (for example `str` or `int`)." + " (options: %s)" % (typ, names), + DeprecationWarning, + stacklevel=4, + ) + attrs["type"] = Argument._typ_map[typ] # Used in test_parseopt -> test_parse_defaultgetter. - self.type = attrs["type"] - else: - self.type = typ - try: + self.type = attrs["type"] + else: + self.type = typ + try: # Attribute existence is tested in Config._processopt. - self.default = attrs["default"] - except KeyError: - pass - self._set_opt_strings(names) + self.default = attrs["default"] + except KeyError: + pass + self._set_opt_strings(names) dest: Optional[str] = attrs.get("dest") if dest: self.dest = dest @@ -268,136 +268,136 @@ class Argument: 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 - + return self._short_opts + self._long_opts + def attrs(self) -> Mapping[str, Any]: # Update any attributes set by processopt. - attrs = "default dest help".split() + attrs = "default dest help".split() attrs.append(self.dest) - for attr in attrs: - try: - self._attrs[attr] = getattr(self, attr) - except AttributeError: - pass - if self._attrs.get("help"): - a = self._attrs["help"] - a = a.replace("%default", "%(default)s") - # a = a.replace('%prog', '%(prog)s') - self._attrs["help"] = a - return self._attrs - + for attr in attrs: + try: + self._attrs[attr] = getattr(self, attr) + except AttributeError: + pass + if self._attrs.get("help"): + a = self._attrs["help"] + a = a.replace("%default", "%(default)s") + # a = a.replace('%prog', '%(prog)s') + self._attrs["help"] = a + return self._attrs + def _set_opt_strings(self, opts: Sequence[str]) -> None: """Directly from optparse. - + Might not be necessary as this is passed to argparse later on. """ - for opt in opts: - if len(opt) < 2: - raise ArgumentError( - "invalid option string %r: " - "must be at least two characters long" % opt, - self, - ) - elif len(opt) == 2: - if not (opt[0] == "-" and opt[1] != "-"): - raise ArgumentError( - "invalid short option string %r: " - "must be of the form -x, (x any non-dash char)" % opt, - self, - ) - self._short_opts.append(opt) - else: - if not (opt[0:2] == "--" and opt[2] != "-"): - raise ArgumentError( - "invalid long option string %r: " - "must start with --, followed by non-dash" % opt, - self, - ) - self._long_opts.append(opt) - + for opt in opts: + if len(opt) < 2: + raise ArgumentError( + "invalid option string %r: " + "must be at least two characters long" % opt, + self, + ) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise ArgumentError( + "invalid short option string %r: " + "must be of the form -x, (x any non-dash char)" % opt, + self, + ) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise ArgumentError( + "invalid long option string %r: " + "must start with --, followed by non-dash" % opt, + self, + ) + self._long_opts.append(opt) + def __repr__(self) -> str: args: List[str] = [] - if self._short_opts: - args += ["_short_opts: " + repr(self._short_opts)] - if self._long_opts: - args += ["_long_opts: " + repr(self._long_opts)] - args += ["dest: " + repr(self.dest)] - if hasattr(self, "type"): - args += ["type: " + repr(self.type)] - if hasattr(self, "default"): - args += ["default: " + repr(self.default)] - return "Argument({})".format(", ".join(args)) - - + if self._short_opts: + args += ["_short_opts: " + repr(self._short_opts)] + if self._long_opts: + args += ["_long_opts: " + repr(self._long_opts)] + args += ["dest: " + repr(self.dest)] + if hasattr(self, "type"): + args += ["type: " + repr(self.type)] + if hasattr(self, "default"): + args += ["default: " + repr(self.default)] + return "Argument({})".format(", ".join(args)) + + class OptionGroup: def __init__( self, name: str, description: str = "", parser: Optional[Parser] = None ) -> None: - self.name = name - self.description = description + self.name = name + self.description = description self.options: List[Argument] = [] - self.parser = parser - + self.parser = parser + 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 - be suppressed in the help. addoption('--twowords', '--two-words') - results in help showing '--two-words' only, but --twowords gets + 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. - """ - conflict = set(optnames).intersection( - name for opt in self.options for name in opt.names() - ) - if conflict: - raise ValueError("option names %s already added" % conflict) - option = Argument(*optnames, **attrs) - self._addoption_instance(option, shortupper=False) - + """ + conflict = set(optnames).intersection( + name for opt in self.options for name in opt.names() + ) + if conflict: + raise ValueError("option names %s already added" % conflict) + option = Argument(*optnames, **attrs) + self._addoption_instance(option, shortupper=False) + def _addoption(self, *optnames: str, **attrs: Any) -> None: - option = Argument(*optnames, **attrs) - self._addoption_instance(option, shortupper=True) - + option = Argument(*optnames, **attrs) + self._addoption_instance(option, shortupper=True) + 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(): - raise ValueError("lowercase shortoptions reserved") - if self.parser: - self.parser.processoption(option) - self.options.append(option) - - -class MyOptionParser(argparse.ArgumentParser): + if not shortupper: + for opt in option._short_opts: + if opt[0] == "-" and opt[1].islower(): + raise ValueError("lowercase shortoptions reserved") + if self.parser: + self.parser.processoption(option) + self.options.append(option) + + +class MyOptionParser(argparse.ArgumentParser): def __init__( self, parser: Parser, extra_info: Optional[Dict[str, Any]] = None, prog: Optional[str] = None, ) -> None: - self._parser = parser - argparse.ArgumentParser.__init__( - self, + self._parser = parser + argparse.ArgumentParser.__init__( + self, prog=prog, - usage=parser._usage, - add_help=False, - formatter_class=DropShorterLongHelpFormatter, + usage=parser._usage, + add_help=False, + formatter_class=DropShorterLongHelpFormatter, allow_abbrev=False, - ) - # extra_info is a dict of (param -> value) to display if there's + ) + # 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. @@ -410,14 +410,14 @@ class MyOptionParser(argparse.ArgumentParser): parsed, unrecognized = self.parse_known_args(args, namespace) if unrecognized: for arg in unrecognized: - if arg and arg[0] == "-": + if arg and arg[0] == "-": lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))] - for k, v in sorted(self.extra_info.items()): + for k, v in sorted(self.extra_info.items()): lines.append(f" {k}: {v}") - self.error("\n".join(lines)) + 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. @@ -455,16 +455,16 @@ class MyOptionParser(argparse.ArgumentParser): if " " in arg_string: return None return None, arg_string, None - -class DropShorterLongHelpFormatter(argparse.HelpFormatter): + +class DropShorterLongHelpFormatter(argparse.HelpFormatter): """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. - """ - + """ + def __init__(self, *args: Any, **kwargs: Any) -> None: # Use more accurate terminal width. if "width" not in kwargs: @@ -472,39 +472,39 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): 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 + 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) - 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' + 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 - return orgstr - return_list = [] + return orgstr + return_list = [] short_long: Dict[str, str] = {} - for option in options: - if len(option) == 2 or option[2] == " ": - continue - if not option.startswith("--"): - raise ArgumentError( + 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 - ) - xxoption = option[2:] + ) + xxoption = option[2:] 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: - if len(option) == 2 or option[2] == " ": - return_list.append(option) - if option[2:] == short_long.get(option.replace("-", "")): - return_list.append(option.replace(" ", "=", 1)) + # 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: + if len(option) == 2 or option[2] == " ": + 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 diff --git a/contrib/python/pytest/py3/_pytest/config/exceptions.py b/contrib/python/pytest/py3/_pytest/config/exceptions.py index a6441629b2c..4f1320e758d 100644 --- a/contrib/python/pytest/py3/_pytest/config/exceptions.py +++ b/contrib/python/pytest/py3/_pytest/config/exceptions.py @@ -2,10 +2,10 @@ from _pytest.compat import final @final -class UsageError(Exception): +class UsageError(Exception): """Error in pytest usage or invocation.""" - - -class PrintHelp(Exception): + + +class PrintHelp(Exception): """Raised when pytest should print its help to skip the rest of the - argument parsing and validation.""" + 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 7eaa4e5c35f..2edf54536ba 100644 --- a/contrib/python/pytest/py3/_pytest/config/findpaths.py +++ b/contrib/python/pytest/py3/_pytest/config/findpaths.py @@ -1,4 +1,4 @@ -import os +import os from pathlib import Path from typing import Dict from typing import Iterable @@ -8,17 +8,17 @@ from typing import Sequence from typing import Tuple from typing import TYPE_CHECKING from typing import Union - + import iniconfig - -from .exceptions import UsageError + +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 @@ -26,20 +26,20 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig: Raise UsageError if the file cannot be parsed. """ - try: + try: 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. - """ - + """ + # Configuration from ini files are obtained from the [pytest] section, if present. if filepath.suffix == ".ini": iniconfig = _parse_ini_config(filepath) @@ -94,10 +94,10 @@ def locate_config( "tox.ini", "setup.cfg", ] - args = [x for x in args if not str(x).startswith("-")] - if not args: + args = [x for x in args if not str(x).startswith("-")] + if not args: args = [Path.cwd()] - for arg in args: + for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): for config_name in config_names: @@ -107,43 +107,43 @@ def locate_config( 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: + 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: - continue + continue elif path in common_ancestor.parents: - common_ancestor = path - else: + common_ancestor = path + else: shared = commonpath(path, common_ancestor) - if shared is not None: - common_ancestor = shared - if common_ancestor is None: + 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 - return common_ancestor - - + return common_ancestor + + 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_dir_from_path(path: Path) -> Path: if path.is_dir(): - return path + 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) @@ -152,16 +152,16 @@ def get_dirs_from_args(args: Iterable[str]) -> List[Path]: except OSError: return False - # These look like paths but may not exist - possible_paths = ( + # These look like paths but may not exist + possible_paths = ( absolutepath(get_file_part_from_node_id(arg)) - for arg in args - if not is_option(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." @@ -172,40 +172,40 @@ def determine_setup( config: Optional["Config"] = None, ) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: rootdir = None - dirs = get_dirs_from_args(args) - if inifile: + 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) - else: - ancestor = 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 - break - else: + break + else: if dirs != [ancestor]: rootdir, inipath, inicfg = locate_config(dirs) - if rootdir is None: + if rootdir is None: 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: + 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(): - raise UsageError( - "Directory '{}' not found. Check your '--rootdir' option.".format( + raise UsageError( + "Directory '{}' not found. Check your '--rootdir' option.".format( rootdir - ) - ) + ) + ) assert rootdir is not None return rootdir, inipath, inicfg or {} |
