diff options
author | deshevoy <deshevoy@yandex-team.ru> | 2022-02-10 16:46:57 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:46:57 +0300 |
commit | 28148f76dbfcc644d96427d41c92f36cbf2fdc6e (patch) | |
tree | b83306b6e37edeea782e9eed673d89286c4fef35 /contrib/python/pytest/py3/_pytest/config/argparsing.py | |
parent | e988f30484abe5fdeedcc7a5d3c226c01a21800c (diff) | |
download | ydb-28148f76dbfcc644d96427d41c92f36cbf2fdc6e.tar.gz |
Restoring authorship annotation for <deshevoy@yandex-team.ru>. 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 | 534 |
1 files changed, 267 insertions, 267 deletions
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 |