diff options
| author | shadchin <[email protected]> | 2022-02-10 16:44:39 +0300 |
|---|---|---|
| committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:44:39 +0300 |
| commit | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (patch) | |
| tree | 64175d5cadab313b3e7039ebaa06c5bc3295e274 /contrib/python/pytest/py3/_pytest/config/argparsing.py | |
| parent | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (diff) | |
Restoring authorship annotation for <[email protected]>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/config/argparsing.py')
| -rw-r--r-- | contrib/python/pytest/py3/_pytest/config/argparsing.py | 528 |
1 files changed, 264 insertions, 264 deletions
diff --git a/contrib/python/pytest/py3/_pytest/config/argparsing.py b/contrib/python/pytest/py3/_pytest/config/argparsing.py index 24188312492..9a481965526 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 +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 -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. +class Argument: + """Class that mimics the necessary behaviour of optparse.Option. + + It's currently a least effort implementation and ignoring choices + and integer prefixes. - 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 |
