Add type annotations to _pytest.config.argparsing and corresponding Config code
This commit is contained in:
parent
51f9cd0e02
commit
dac16cd9e5
|
@ -53,19 +53,22 @@ If things do not work right away:
|
||||||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||||
global argcomplete script).
|
global argcomplete script).
|
||||||
"""
|
"""
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
from typing import Any
|
||||||
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class FastFilesCompleter:
|
class FastFilesCompleter:
|
||||||
"Fast file completer class"
|
"Fast file completer class"
|
||||||
|
|
||||||
def __init__(self, directories=True):
|
def __init__(self, directories: bool = True) -> None:
|
||||||
self.directories = directories
|
self.directories = directories
|
||||||
|
|
||||||
def __call__(self, prefix, **kwargs):
|
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
|
||||||
"""only called on non option completions"""
|
"""only called on non option completions"""
|
||||||
if os.path.sep in prefix[1:]:
|
if os.path.sep in prefix[1:]:
|
||||||
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
|
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
|
||||||
|
@ -94,13 +97,13 @@ if os.environ.get("_ARGCOMPLETE"):
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
|
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
|
||||||
|
|
||||||
def try_argcomplete(parser):
|
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||||
argcomplete.autocomplete(parser, always_complete_options=False)
|
argcomplete.autocomplete(parser, always_complete_options=False)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def try_argcomplete(parser):
|
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
filescompleter = None
|
filescompleter = None
|
||||||
|
|
|
@ -45,6 +45,8 @@ from _pytest.warning_types import PytestConfigWarning
|
||||||
if False: # TYPE_CHECKING
|
if False: # TYPE_CHECKING
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from .argparsing import Argument
|
||||||
|
|
||||||
|
|
||||||
hookimpl = HookimplMarker("pytest")
|
hookimpl = HookimplMarker("pytest")
|
||||||
hookspec = HookspecMarker("pytest")
|
hookspec = HookspecMarker("pytest")
|
||||||
|
@ -679,7 +681,7 @@ class Config:
|
||||||
plugins = attr.ib()
|
plugins = attr.ib()
|
||||||
dir = attr.ib(type=Path)
|
dir = attr.ib(type=Path)
|
||||||
|
|
||||||
def __init__(self, pluginmanager, *, invocation_params=None):
|
def __init__(self, pluginmanager, *, invocation_params=None) -> None:
|
||||||
from .argparsing import Parser, FILE_OR_DIR
|
from .argparsing import Parser, FILE_OR_DIR
|
||||||
|
|
||||||
if invocation_params is None:
|
if invocation_params is None:
|
||||||
|
@ -792,11 +794,11 @@ class Config:
|
||||||
config.pluginmanager.consider_pluginarg(x)
|
config.pluginmanager.consider_pluginarg(x)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _processopt(self, opt):
|
def _processopt(self, opt: "Argument") -> None:
|
||||||
for name in opt._short_opts + opt._long_opts:
|
for name in opt._short_opts + opt._long_opts:
|
||||||
self._opt2dest[name] = opt.dest
|
self._opt2dest[name] = opt.dest
|
||||||
|
|
||||||
if hasattr(opt, "default") and opt.dest:
|
if hasattr(opt, "default"):
|
||||||
if not hasattr(self.option, opt.dest):
|
if not hasattr(self.option, opt.dest):
|
||||||
setattr(self.option, opt.dest, opt.default)
|
setattr(self.option, opt.dest, opt.default)
|
||||||
|
|
||||||
|
@ -804,7 +806,7 @@ class Config:
|
||||||
def pytest_load_initial_conftests(self, early_config):
|
def pytest_load_initial_conftests(self, early_config):
|
||||||
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
|
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
|
||||||
|
|
||||||
def _initini(self, args) -> None:
|
def _initini(self, args: Sequence[str]) -> None:
|
||||||
ns, unknown_args = self._parser.parse_known_and_unknown_args(
|
ns, unknown_args = self._parser.parse_known_and_unknown_args(
|
||||||
args, namespace=copy.copy(self.option)
|
args, namespace=copy.copy(self.option)
|
||||||
)
|
)
|
||||||
|
@ -821,7 +823,7 @@ class Config:
|
||||||
self._parser.addini("minversion", "minimally required pytest version")
|
self._parser.addini("minversion", "minimally required pytest version")
|
||||||
self._override_ini = ns.override_ini or ()
|
self._override_ini = ns.override_ini or ()
|
||||||
|
|
||||||
def _consider_importhook(self, args):
|
def _consider_importhook(self, args: Sequence[str]) -> None:
|
||||||
"""Install the PEP 302 import hook if using assertion rewriting.
|
"""Install the PEP 302 import hook if using assertion rewriting.
|
||||||
|
|
||||||
Needs to parse the --assert=<mode> option from the commandline
|
Needs to parse the --assert=<mode> option from the commandline
|
||||||
|
@ -861,19 +863,19 @@ class Config:
|
||||||
for name in _iter_rewritable_modules(package_files):
|
for name in _iter_rewritable_modules(package_files):
|
||||||
hook.mark_rewrite(name)
|
hook.mark_rewrite(name)
|
||||||
|
|
||||||
def _validate_args(self, args, via):
|
def _validate_args(self, args: List[str], via: str) -> List[str]:
|
||||||
"""Validate known args."""
|
"""Validate known args."""
|
||||||
self._parser._config_source_hint = via
|
self._parser._config_source_hint = via # type: ignore
|
||||||
try:
|
try:
|
||||||
self._parser.parse_known_and_unknown_args(
|
self._parser.parse_known_and_unknown_args(
|
||||||
args, namespace=copy.copy(self.option)
|
args, namespace=copy.copy(self.option)
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
del self._parser._config_source_hint
|
del self._parser._config_source_hint # type: ignore
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def _preparse(self, args, addopts=True):
|
def _preparse(self, args: List[str], addopts: bool = True) -> None:
|
||||||
if addopts:
|
if addopts:
|
||||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||||
if len(env_addopts):
|
if len(env_addopts):
|
||||||
|
@ -937,7 +939,7 @@ class Config:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse(self, args, addopts=True):
|
def parse(self, args: List[str], addopts: bool = True) -> None:
|
||||||
# parse given cmdline arguments into this config object.
|
# parse given cmdline arguments into this config object.
|
||||||
assert not hasattr(
|
assert not hasattr(
|
||||||
self, "args"
|
self, "args"
|
||||||
|
@ -948,7 +950,7 @@ class Config:
|
||||||
self._preparse(args, addopts=addopts)
|
self._preparse(args, addopts=addopts)
|
||||||
# XXX deprecated hook:
|
# XXX deprecated hook:
|
||||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||||
self._parser.after_preparse = True
|
self._parser.after_preparse = True # type: ignore
|
||||||
try:
|
try:
|
||||||
args = self._parser.parse_setoption(
|
args = self._parser.parse_setoption(
|
||||||
args, self.option, namespace=self.option
|
args, self.option, namespace=self.option
|
||||||
|
|
|
@ -3,15 +3,24 @@ import sys
|
||||||
import warnings
|
import warnings
|
||||||
from gettext import gettext
|
from gettext import gettext
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
|
if False: # TYPE_CHECKING
|
||||||
|
from typing import NoReturn
|
||||||
|
from typing_extensions import Literal # noqa: F401
|
||||||
|
|
||||||
FILE_OR_DIR = "file_or_dir"
|
FILE_OR_DIR = "file_or_dir"
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,9 +31,13 @@ class Parser:
|
||||||
there's an error processing the command line arguments.
|
there's an error processing the command line arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prog = None
|
prog = None # type: Optional[str]
|
||||||
|
|
||||||
def __init__(self, usage=None, processopt=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
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 = [] # type: List[OptionGroup]
|
self._groups = [] # type: List[OptionGroup]
|
||||||
self._processopt = processopt
|
self._processopt = processopt
|
||||||
|
@ -33,12 +46,14 @@ class Parser:
|
||||||
self._ininames = [] # type: List[str]
|
self._ininames = [] # type: List[str]
|
||||||
self.extra_info = {} # type: Dict[str, Any]
|
self.extra_info = {} # type: Dict[str, Any]
|
||||||
|
|
||||||
def processoption(self, option):
|
def processoption(self, option: "Argument") -> None:
|
||||||
if self._processopt:
|
if self._processopt:
|
||||||
if option.dest:
|
if option.dest:
|
||||||
self._processopt(option)
|
self._processopt(option)
|
||||||
|
|
||||||
def getgroup(self, name, description="", after=None):
|
def getgroup(
|
||||||
|
self, name: str, description: str = "", after: Optional[str] = None
|
||||||
|
) -> "OptionGroup":
|
||||||
""" get (or create) a named option Group.
|
""" get (or create) a named option Group.
|
||||||
|
|
||||||
:name: name of the option group.
|
:name: name of the option group.
|
||||||
|
@ -61,13 +76,13 @@ class Parser:
|
||||||
self._groups.insert(i + 1, group)
|
self._groups.insert(i + 1, group)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def addoption(self, *opts, **attrs):
|
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||||
""" register a command line option.
|
""" register a command line option.
|
||||||
|
|
||||||
:opts: option names, can be short or long options.
|
:opts: option names, can be short or long options.
|
||||||
:attrs: same attributes which the ``add_option()`` function of the
|
:attrs: same attributes which the ``add_argument()`` function of the
|
||||||
`argparse library
|
`argparse library
|
||||||
<http://docs.python.org/2/library/argparse.html>`_
|
<https://docs.python.org/library/argparse.html>`_
|
||||||
accepts.
|
accepts.
|
||||||
|
|
||||||
After command line parsing options are available on the pytest config
|
After command line parsing options are available on the pytest config
|
||||||
|
@ -77,7 +92,11 @@ class Parser:
|
||||||
"""
|
"""
|
||||||
self._anonymous.addoption(*opts, **attrs)
|
self._anonymous.addoption(*opts, **attrs)
|
||||||
|
|
||||||
def parse(self, args, namespace=None):
|
def parse(
|
||||||
|
self,
|
||||||
|
args: Sequence[Union[str, py.path.local]],
|
||||||
|
namespace: Optional[argparse.Namespace] = None,
|
||||||
|
) -> argparse.Namespace:
|
||||||
from _pytest._argcomplete import try_argcomplete
|
from _pytest._argcomplete import try_argcomplete
|
||||||
|
|
||||||
self.optparser = self._getparser()
|
self.optparser = self._getparser()
|
||||||
|
@ -98,27 +117,37 @@ class Parser:
|
||||||
n = option.names()
|
n = option.names()
|
||||||
a = option.attrs()
|
a = option.attrs()
|
||||||
arggroup.add_argument(*n, **a)
|
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.
|
# Type ignored because typeshed doesn't know about argcomplete.
|
||||||
optparser.add_argument( # type: ignore
|
file_or_dir_arg.completer = filescompleter # type: ignore
|
||||||
FILE_OR_DIR, nargs="*"
|
|
||||||
).completer = filescompleter
|
|
||||||
return optparser
|
return optparser
|
||||||
|
|
||||||
def parse_setoption(self, args, option, namespace=None):
|
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)
|
parsedoption = self.parse(args, namespace=namespace)
|
||||||
for name, value in parsedoption.__dict__.items():
|
for name, value in parsedoption.__dict__.items():
|
||||||
setattr(option, name, value)
|
setattr(option, name, value)
|
||||||
return getattr(parsedoption, FILE_OR_DIR)
|
return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
|
||||||
|
|
||||||
def parse_known_args(self, args, namespace=None) -> argparse.Namespace:
|
def parse_known_args(
|
||||||
|
self,
|
||||||
|
args: Sequence[Union[str, py.path.local]],
|
||||||
|
namespace: Optional[argparse.Namespace] = None,
|
||||||
|
) -> argparse.Namespace:
|
||||||
"""parses and returns a namespace object with known arguments at this
|
"""parses and returns a namespace object with known arguments at this
|
||||||
point.
|
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(
|
def parse_known_and_unknown_args(
|
||||||
self, args, namespace=None
|
self,
|
||||||
|
args: Sequence[Union[str, py.path.local]],
|
||||||
|
namespace: Optional[argparse.Namespace] = None,
|
||||||
) -> Tuple[argparse.Namespace, List[str]]:
|
) -> Tuple[argparse.Namespace, List[str]]:
|
||||||
"""parses and returns a namespace object with known arguments, and
|
"""parses and returns a namespace object with known arguments, and
|
||||||
the remaining arguments unknown at this point.
|
the remaining arguments unknown at this point.
|
||||||
|
@ -127,7 +156,13 @@ class Parser:
|
||||||
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
|
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
|
||||||
return optparser.parse_known_args(args, namespace=namespace)
|
return optparser.parse_known_args(args, namespace=namespace)
|
||||||
|
|
||||||
def addini(self, name, help, type=None, default=None):
|
def addini(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
help: str,
|
||||||
|
type: Optional["Literal['pathlist', 'args', 'linelist', 'bool']"] = None,
|
||||||
|
default=None,
|
||||||
|
) -> None:
|
||||||
""" register an ini-file option.
|
""" register an ini-file option.
|
||||||
|
|
||||||
:name: name of the ini-variable
|
:name: name of the ini-variable
|
||||||
|
@ -149,11 +184,11 @@ class ArgumentError(Exception):
|
||||||
inconsistent arguments.
|
inconsistent arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg, option):
|
def __init__(self, msg: str, option: Union["Argument", str]) -> None:
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.option_id = str(option)
|
self.option_id = str(option)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
if self.option_id:
|
if self.option_id:
|
||||||
return "option {}: {}".format(self.option_id, self.msg)
|
return "option {}: {}".format(self.option_id, self.msg)
|
||||||
else:
|
else:
|
||||||
|
@ -170,12 +205,11 @@ class Argument:
|
||||||
|
|
||||||
_typ_map = {"int": int, "string": str, "float": float, "complex": complex}
|
_typ_map = {"int": int, "string": str, "float": float, "complex": complex}
|
||||||
|
|
||||||
def __init__(self, *names, **attrs):
|
def __init__(self, *names: str, **attrs: Any) -> None:
|
||||||
"""store parms in private vars for use in add_argument"""
|
"""store parms in private vars for use in add_argument"""
|
||||||
self._attrs = attrs
|
self._attrs = attrs
|
||||||
self._short_opts = [] # type: List[str]
|
self._short_opts = [] # type: List[str]
|
||||||
self._long_opts = [] # type: List[str]
|
self._long_opts = [] # type: List[str]
|
||||||
self.dest = attrs.get("dest")
|
|
||||||
if "%default" in (attrs.get("help") or ""):
|
if "%default" in (attrs.get("help") or ""):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'pytest now uses argparse. "%default" should be'
|
'pytest now uses argparse. "%default" should be'
|
||||||
|
@ -221,23 +255,25 @@ class Argument:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
self._set_opt_strings(names)
|
self._set_opt_strings(names)
|
||||||
if not self.dest:
|
dest = attrs.get("dest") # type: Optional[str]
|
||||||
if self._long_opts:
|
if dest:
|
||||||
self.dest = self._long_opts[0][2:].replace("-", "_")
|
self.dest = dest
|
||||||
else:
|
elif self._long_opts:
|
||||||
try:
|
self.dest = self._long_opts[0][2:].replace("-", "_")
|
||||||
self.dest = self._short_opts[0][1:]
|
else:
|
||||||
except IndexError:
|
try:
|
||||||
raise ArgumentError("need a long or short option", self)
|
self.dest = self._short_opts[0][1:]
|
||||||
|
except IndexError:
|
||||||
|
self.dest = "???" # Needed for the error repr.
|
||||||
|
raise ArgumentError("need a long or short option", self)
|
||||||
|
|
||||||
def names(self):
|
def names(self) -> List[str]:
|
||||||
return self._short_opts + self._long_opts
|
return self._short_opts + self._long_opts
|
||||||
|
|
||||||
def attrs(self):
|
def attrs(self) -> Mapping[str, Any]:
|
||||||
# update any attributes set by processopt
|
# update any attributes set by processopt
|
||||||
attrs = "default dest help".split()
|
attrs = "default dest help".split()
|
||||||
if self.dest:
|
attrs.append(self.dest)
|
||||||
attrs.append(self.dest)
|
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
try:
|
try:
|
||||||
self._attrs[attr] = getattr(self, attr)
|
self._attrs[attr] = getattr(self, attr)
|
||||||
|
@ -250,7 +286,7 @@ class Argument:
|
||||||
self._attrs["help"] = a
|
self._attrs["help"] = a
|
||||||
return self._attrs
|
return self._attrs
|
||||||
|
|
||||||
def _set_opt_strings(self, opts):
|
def _set_opt_strings(self, opts: Sequence[str]) -> None:
|
||||||
"""directly from optparse
|
"""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"""
|
||||||
|
@ -293,13 +329,15 @@ class Argument:
|
||||||
|
|
||||||
|
|
||||||
class OptionGroup:
|
class OptionGroup:
|
||||||
def __init__(self, name, description="", parser=None):
|
def __init__(
|
||||||
|
self, name: str, description: str = "", parser: Optional[Parser] = None
|
||||||
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.options = [] # type: List[Argument]
|
self.options = [] # type: List[Argument]
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
|
||||||
def addoption(self, *optnames, **attrs):
|
def addoption(self, *optnames: str, **attrs: Any) -> None:
|
||||||
""" add an option to this group.
|
""" 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
|
||||||
|
@ -315,11 +353,11 @@ class OptionGroup:
|
||||||
option = Argument(*optnames, **attrs)
|
option = Argument(*optnames, **attrs)
|
||||||
self._addoption_instance(option, shortupper=False)
|
self._addoption_instance(option, shortupper=False)
|
||||||
|
|
||||||
def _addoption(self, *optnames, **attrs):
|
def _addoption(self, *optnames: str, **attrs: Any) -> None:
|
||||||
option = Argument(*optnames, **attrs)
|
option = Argument(*optnames, **attrs)
|
||||||
self._addoption_instance(option, shortupper=True)
|
self._addoption_instance(option, shortupper=True)
|
||||||
|
|
||||||
def _addoption_instance(self, option, shortupper=False):
|
def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
|
||||||
if not shortupper:
|
if not shortupper:
|
||||||
for opt in option._short_opts:
|
for opt in option._short_opts:
|
||||||
if opt[0] == "-" and opt[1].islower():
|
if opt[0] == "-" and opt[1].islower():
|
||||||
|
@ -330,9 +368,12 @@ class OptionGroup:
|
||||||
|
|
||||||
|
|
||||||
class MyOptionParser(argparse.ArgumentParser):
|
class MyOptionParser(argparse.ArgumentParser):
|
||||||
def __init__(self, parser, extra_info=None, prog=None):
|
def __init__(
|
||||||
if not extra_info:
|
self,
|
||||||
extra_info = {}
|
parser: Parser,
|
||||||
|
extra_info: Optional[Dict[str, Any]] = None,
|
||||||
|
prog: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
self._parser = parser
|
self._parser = parser
|
||||||
argparse.ArgumentParser.__init__(
|
argparse.ArgumentParser.__init__(
|
||||||
self,
|
self,
|
||||||
|
@ -344,34 +385,42 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
)
|
)
|
||||||
# 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
|
# an usage error to provide more contextual information to the user
|
||||||
self.extra_info = extra_info
|
self.extra_info = extra_info if extra_info else {}
|
||||||
|
|
||||||
def error(self, message):
|
def error(self, message: str) -> "NoReturn":
|
||||||
"""Transform argparse error message into UsageError."""
|
"""Transform argparse error message into UsageError."""
|
||||||
msg = "{}: error: {}".format(self.prog, message)
|
msg = "{}: error: {}".format(self.prog, message)
|
||||||
|
|
||||||
if hasattr(self._parser, "_config_source_hint"):
|
if hasattr(self._parser, "_config_source_hint"):
|
||||||
msg = "{} ({})".format(msg, self._parser._config_source_hint)
|
# Type ignored because the attribute is set dynamically.
|
||||||
|
msg = "{} ({})".format(msg, self._parser._config_source_hint) # type: ignore
|
||||||
|
|
||||||
raise UsageError(self.format_usage() + msg)
|
raise UsageError(self.format_usage() + msg)
|
||||||
|
|
||||||
def parse_args(self, args=None, namespace=None):
|
# 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"""
|
"""allow splitting of positional arguments"""
|
||||||
args, argv = self.parse_known_args(args, namespace)
|
parsed, unrecognized = self.parse_known_args(args, namespace)
|
||||||
if argv:
|
if unrecognized:
|
||||||
for arg in argv:
|
for arg in unrecognized:
|
||||||
if arg and arg[0] == "-":
|
if arg and arg[0] == "-":
|
||||||
lines = ["unrecognized arguments: %s" % (" ".join(argv))]
|
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(" {}: {}".format(k, v))
|
lines.append(" {}: {}".format(k, v))
|
||||||
self.error("\n".join(lines))
|
self.error("\n".join(lines))
|
||||||
getattr(args, FILE_OR_DIR).extend(argv)
|
getattr(parsed, FILE_OR_DIR).extend(unrecognized)
|
||||||
return args
|
return parsed
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 9): # pragma: no cover
|
if sys.version_info[:2] < (3, 9): # pragma: no cover
|
||||||
# Backport of https://github.com/python/cpython/pull/14316 so we can
|
# Backport of https://github.com/python/cpython/pull/14316 so we can
|
||||||
# disable long --argument abbreviations without breaking short flags.
|
# disable long --argument abbreviations without breaking short flags.
|
||||||
def _parse_optional(self, arg_string):
|
def _parse_optional(
|
||||||
|
self, arg_string: str
|
||||||
|
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
|
||||||
if not arg_string:
|
if not arg_string:
|
||||||
return None
|
return None
|
||||||
if not arg_string[0] in self.prefix_chars:
|
if not arg_string[0] in self.prefix_chars:
|
||||||
|
@ -413,23 +462,25 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
- cache result on action object as this is called at least 2 times
|
- cache result on action object as this is called at least 2 times
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""Use more accurate terminal width via pylib."""
|
"""Use more accurate terminal width via pylib."""
|
||||||
if "width" not in kwargs:
|
if "width" not in kwargs:
|
||||||
kwargs["width"] = py.io.get_terminal_width()
|
kwargs["width"] = py.io.get_terminal_width()
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _format_action_invocation(self, action):
|
def _format_action_invocation(self, action: argparse.Action) -> str:
|
||||||
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
|
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
|
||||||
if orgstr and orgstr[0] != "-": # only optional arguments
|
if orgstr and orgstr[0] != "-": # only optional arguments
|
||||||
return orgstr
|
return orgstr
|
||||||
res = getattr(action, "_formatted_action_invocation", None)
|
res = getattr(
|
||||||
|
action, "_formatted_action_invocation", None
|
||||||
|
) # type: Optional[str]
|
||||||
if res:
|
if res:
|
||||||
return res
|
return res
|
||||||
options = orgstr.split(", ")
|
options = orgstr.split(", ")
|
||||||
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
|
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
|
||||||
# a shortcut for '-h, --help' or '--abc', '-a'
|
# a shortcut for '-h, --help' or '--abc', '-a'
|
||||||
action._formatted_action_invocation = orgstr
|
action._formatted_action_invocation = orgstr # type: ignore
|
||||||
return orgstr
|
return orgstr
|
||||||
return_list = []
|
return_list = []
|
||||||
short_long = {} # type: Dict[str, str]
|
short_long = {} # type: Dict[str, str]
|
||||||
|
@ -438,7 +489,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
continue
|
continue
|
||||||
if not option.startswith("--"):
|
if not option.startswith("--"):
|
||||||
raise ArgumentError(
|
raise ArgumentError(
|
||||||
'long optional argument without "--": [%s]' % (option), self
|
'long optional argument without "--": [%s]' % (option), option
|
||||||
)
|
)
|
||||||
xxoption = option[2:]
|
xxoption = option[2:]
|
||||||
shortened = xxoption.replace("-", "")
|
shortened = xxoption.replace("-", "")
|
||||||
|
@ -453,5 +504,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
return_list.append(option)
|
return_list.append(option)
|
||||||
if option[2:] == short_long.get(option.replace("-", "")):
|
if option[2:] == short_long.get(option.replace("-", "")):
|
||||||
return_list.append(option.replace(" ", "=", 1))
|
return_list.append(option.replace(" ", "=", 1))
|
||||||
action._formatted_action_invocation = ", ".join(return_list)
|
formatted_action_invocation = ", ".join(return_list)
|
||||||
return action._formatted_action_invocation
|
action._formatted_action_invocation = formatted_action_invocation # type: ignore
|
||||||
|
return formatted_action_invocation
|
||||||
|
|
|
@ -12,22 +12,22 @@ from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def parser():
|
def parser() -> parseopt.Parser:
|
||||||
return parseopt.Parser()
|
return parseopt.Parser()
|
||||||
|
|
||||||
|
|
||||||
class TestParser:
|
class TestParser:
|
||||||
def test_no_help_by_default(self):
|
def test_no_help_by_default(self) -> None:
|
||||||
parser = parseopt.Parser(usage="xyz")
|
parser = parseopt.Parser(usage="xyz")
|
||||||
pytest.raises(UsageError, lambda: parser.parse(["-h"]))
|
pytest.raises(UsageError, lambda: parser.parse(["-h"]))
|
||||||
|
|
||||||
def test_custom_prog(self, parser):
|
def test_custom_prog(self, parser: parseopt.Parser) -> None:
|
||||||
"""Custom prog can be set for `argparse.ArgumentParser`."""
|
"""Custom prog can be set for `argparse.ArgumentParser`."""
|
||||||
assert parser._getparser().prog == os.path.basename(sys.argv[0])
|
assert parser._getparser().prog == os.path.basename(sys.argv[0])
|
||||||
parser.prog = "custom-prog"
|
parser.prog = "custom-prog"
|
||||||
assert parser._getparser().prog == "custom-prog"
|
assert parser._getparser().prog == "custom-prog"
|
||||||
|
|
||||||
def test_argument(self):
|
def test_argument(self) -> None:
|
||||||
with pytest.raises(parseopt.ArgumentError):
|
with pytest.raises(parseopt.ArgumentError):
|
||||||
# need a short or long option
|
# need a short or long option
|
||||||
argument = parseopt.Argument()
|
argument = parseopt.Argument()
|
||||||
|
@ -45,7 +45,7 @@ class TestParser:
|
||||||
"Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
|
"Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_argument_type(self):
|
def test_argument_type(self) -> None:
|
||||||
argument = parseopt.Argument("-t", dest="abc", type=int)
|
argument = parseopt.Argument("-t", dest="abc", type=int)
|
||||||
assert argument.type is int
|
assert argument.type is int
|
||||||
argument = parseopt.Argument("-t", dest="abc", type=str)
|
argument = parseopt.Argument("-t", dest="abc", type=str)
|
||||||
|
@ -60,7 +60,7 @@ class TestParser:
|
||||||
)
|
)
|
||||||
assert argument.type is str
|
assert argument.type is str
|
||||||
|
|
||||||
def test_argument_processopt(self):
|
def test_argument_processopt(self) -> None:
|
||||||
argument = parseopt.Argument("-t", type=int)
|
argument = parseopt.Argument("-t", type=int)
|
||||||
argument.default = 42
|
argument.default = 42
|
||||||
argument.dest = "abc"
|
argument.dest = "abc"
|
||||||
|
@ -68,19 +68,19 @@ class TestParser:
|
||||||
assert res["default"] == 42
|
assert res["default"] == 42
|
||||||
assert res["dest"] == "abc"
|
assert res["dest"] == "abc"
|
||||||
|
|
||||||
def test_group_add_and_get(self, parser):
|
def test_group_add_and_get(self, parser: parseopt.Parser) -> None:
|
||||||
group = parser.getgroup("hello", description="desc")
|
group = parser.getgroup("hello", description="desc")
|
||||||
assert group.name == "hello"
|
assert group.name == "hello"
|
||||||
assert group.description == "desc"
|
assert group.description == "desc"
|
||||||
|
|
||||||
def test_getgroup_simple(self, parser):
|
def test_getgroup_simple(self, parser: parseopt.Parser) -> None:
|
||||||
group = parser.getgroup("hello", description="desc")
|
group = parser.getgroup("hello", description="desc")
|
||||||
assert group.name == "hello"
|
assert group.name == "hello"
|
||||||
assert group.description == "desc"
|
assert group.description == "desc"
|
||||||
group2 = parser.getgroup("hello")
|
group2 = parser.getgroup("hello")
|
||||||
assert group2 is group
|
assert group2 is group
|
||||||
|
|
||||||
def test_group_ordering(self, parser):
|
def test_group_ordering(self, parser: parseopt.Parser) -> None:
|
||||||
parser.getgroup("1")
|
parser.getgroup("1")
|
||||||
parser.getgroup("2")
|
parser.getgroup("2")
|
||||||
parser.getgroup("3", after="1")
|
parser.getgroup("3", after="1")
|
||||||
|
@ -88,20 +88,20 @@ class TestParser:
|
||||||
groups_names = [x.name for x in groups]
|
groups_names = [x.name for x in groups]
|
||||||
assert groups_names == list("132")
|
assert groups_names == list("132")
|
||||||
|
|
||||||
def test_group_addoption(self):
|
def test_group_addoption(self) -> None:
|
||||||
group = parseopt.OptionGroup("hello")
|
group = parseopt.OptionGroup("hello")
|
||||||
group.addoption("--option1", action="store_true")
|
group.addoption("--option1", action="store_true")
|
||||||
assert len(group.options) == 1
|
assert len(group.options) == 1
|
||||||
assert isinstance(group.options[0], parseopt.Argument)
|
assert isinstance(group.options[0], parseopt.Argument)
|
||||||
|
|
||||||
def test_group_addoption_conflict(self):
|
def test_group_addoption_conflict(self) -> None:
|
||||||
group = parseopt.OptionGroup("hello again")
|
group = parseopt.OptionGroup("hello again")
|
||||||
group.addoption("--option1", "--option-1", action="store_true")
|
group.addoption("--option1", "--option-1", action="store_true")
|
||||||
with pytest.raises(ValueError) as err:
|
with pytest.raises(ValueError) as err:
|
||||||
group.addoption("--option1", "--option-one", action="store_true")
|
group.addoption("--option1", "--option-one", action="store_true")
|
||||||
assert str({"--option1"}) in str(err.value)
|
assert str({"--option1"}) in str(err.value)
|
||||||
|
|
||||||
def test_group_shortopt_lowercase(self, parser):
|
def test_group_shortopt_lowercase(self, parser: parseopt.Parser) -> None:
|
||||||
group = parser.getgroup("hello")
|
group = parser.getgroup("hello")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
group.addoption("-x", action="store_true")
|
group.addoption("-x", action="store_true")
|
||||||
|
@ -109,30 +109,30 @@ class TestParser:
|
||||||
group._addoption("-x", action="store_true")
|
group._addoption("-x", action="store_true")
|
||||||
assert len(group.options) == 1
|
assert len(group.options) == 1
|
||||||
|
|
||||||
def test_parser_addoption(self, parser):
|
def test_parser_addoption(self, parser: parseopt.Parser) -> None:
|
||||||
group = parser.getgroup("custom options")
|
group = parser.getgroup("custom options")
|
||||||
assert len(group.options) == 0
|
assert len(group.options) == 0
|
||||||
group.addoption("--option1", action="store_true")
|
group.addoption("--option1", action="store_true")
|
||||||
assert len(group.options) == 1
|
assert len(group.options) == 1
|
||||||
|
|
||||||
def test_parse(self, parser):
|
def test_parse(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--hello", dest="hello", action="store")
|
parser.addoption("--hello", dest="hello", action="store")
|
||||||
args = parser.parse(["--hello", "world"])
|
args = parser.parse(["--hello", "world"])
|
||||||
assert args.hello == "world"
|
assert args.hello == "world"
|
||||||
assert not getattr(args, parseopt.FILE_OR_DIR)
|
assert not getattr(args, parseopt.FILE_OR_DIR)
|
||||||
|
|
||||||
def test_parse2(self, parser):
|
def test_parse2(self, parser: parseopt.Parser) -> None:
|
||||||
args = parser.parse([py.path.local()])
|
args = parser.parse([py.path.local()])
|
||||||
assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()
|
assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local()
|
||||||
|
|
||||||
def test_parse_known_args(self, parser):
|
def test_parse_known_args(self, parser: parseopt.Parser) -> None:
|
||||||
parser.parse_known_args([py.path.local()])
|
parser.parse_known_args([py.path.local()])
|
||||||
parser.addoption("--hello", action="store_true")
|
parser.addoption("--hello", action="store_true")
|
||||||
ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
|
ns = parser.parse_known_args(["x", "--y", "--hello", "this"])
|
||||||
assert ns.hello
|
assert ns.hello
|
||||||
assert ns.file_or_dir == ["x"]
|
assert ns.file_or_dir == ["x"]
|
||||||
|
|
||||||
def test_parse_known_and_unknown_args(self, parser):
|
def test_parse_known_and_unknown_args(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--hello", action="store_true")
|
parser.addoption("--hello", action="store_true")
|
||||||
ns, unknown = parser.parse_known_and_unknown_args(
|
ns, unknown = parser.parse_known_and_unknown_args(
|
||||||
["x", "--y", "--hello", "this"]
|
["x", "--y", "--hello", "this"]
|
||||||
|
@ -141,7 +141,7 @@ class TestParser:
|
||||||
assert ns.file_or_dir == ["x"]
|
assert ns.file_or_dir == ["x"]
|
||||||
assert unknown == ["--y", "this"]
|
assert unknown == ["--y", "this"]
|
||||||
|
|
||||||
def test_parse_will_set_default(self, parser):
|
def test_parse_will_set_default(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--hello", dest="hello", default="x", action="store")
|
parser.addoption("--hello", dest="hello", default="x", action="store")
|
||||||
option = parser.parse([])
|
option = parser.parse([])
|
||||||
assert option.hello == "x"
|
assert option.hello == "x"
|
||||||
|
@ -149,25 +149,22 @@ class TestParser:
|
||||||
parser.parse_setoption([], option)
|
parser.parse_setoption([], option)
|
||||||
assert option.hello == "x"
|
assert option.hello == "x"
|
||||||
|
|
||||||
def test_parse_setoption(self, parser):
|
def test_parse_setoption(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--hello", dest="hello", action="store")
|
parser.addoption("--hello", dest="hello", action="store")
|
||||||
parser.addoption("--world", dest="world", default=42)
|
parser.addoption("--world", dest="world", default=42)
|
||||||
|
|
||||||
class A:
|
option = argparse.Namespace()
|
||||||
pass
|
|
||||||
|
|
||||||
option = A()
|
|
||||||
args = parser.parse_setoption(["--hello", "world"], option)
|
args = parser.parse_setoption(["--hello", "world"], option)
|
||||||
assert option.hello == "world"
|
assert option.hello == "world"
|
||||||
assert option.world == 42
|
assert option.world == 42
|
||||||
assert not args
|
assert not args
|
||||||
|
|
||||||
def test_parse_special_destination(self, parser):
|
def test_parse_special_destination(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--ultimate-answer", type=int)
|
parser.addoption("--ultimate-answer", type=int)
|
||||||
args = parser.parse(["--ultimate-answer", "42"])
|
args = parser.parse(["--ultimate-answer", "42"])
|
||||||
assert args.ultimate_answer == 42
|
assert args.ultimate_answer == 42
|
||||||
|
|
||||||
def test_parse_split_positional_arguments(self, parser):
|
def test_parse_split_positional_arguments(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("-R", action="store_true")
|
parser.addoption("-R", action="store_true")
|
||||||
parser.addoption("-S", action="store_false")
|
parser.addoption("-S", action="store_false")
|
||||||
args = parser.parse(["-R", "4", "2", "-S"])
|
args = parser.parse(["-R", "4", "2", "-S"])
|
||||||
|
@ -181,7 +178,7 @@ class TestParser:
|
||||||
assert args.R is True
|
assert args.R is True
|
||||||
assert args.S is False
|
assert args.S is False
|
||||||
|
|
||||||
def test_parse_defaultgetter(self):
|
def test_parse_defaultgetter(self) -> None:
|
||||||
def defaultget(option):
|
def defaultget(option):
|
||||||
if not hasattr(option, "type"):
|
if not hasattr(option, "type"):
|
||||||
return
|
return
|
||||||
|
@ -199,7 +196,7 @@ class TestParser:
|
||||||
assert option.this == 42
|
assert option.this == 42
|
||||||
assert option.no is False
|
assert option.no is False
|
||||||
|
|
||||||
def test_drop_short_helper(self):
|
def test_drop_short_helper(self) -> None:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False
|
formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False
|
||||||
)
|
)
|
||||||
|
@ -236,32 +233,32 @@ class TestParser:
|
||||||
args = parser.parse_args(["file", "dir"])
|
args = parser.parse_args(["file", "dir"])
|
||||||
assert "|".join(args.files_and_dirs) == "file|dir"
|
assert "|".join(args.files_and_dirs) == "file|dir"
|
||||||
|
|
||||||
def test_drop_short_0(self, parser):
|
def test_drop_short_0(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--funcarg", "--func-arg", action="store_true")
|
parser.addoption("--funcarg", "--func-arg", action="store_true")
|
||||||
parser.addoption("--abc-def", "--abc-def", action="store_true")
|
parser.addoption("--abc-def", "--abc-def", action="store_true")
|
||||||
parser.addoption("--klm-hij", action="store_true")
|
parser.addoption("--klm-hij", action="store_true")
|
||||||
with pytest.raises(UsageError):
|
with pytest.raises(UsageError):
|
||||||
parser.parse(["--funcarg", "--k"])
|
parser.parse(["--funcarg", "--k"])
|
||||||
|
|
||||||
def test_drop_short_2(self, parser):
|
def test_drop_short_2(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--func-arg", "--doit", action="store_true")
|
parser.addoption("--func-arg", "--doit", action="store_true")
|
||||||
args = parser.parse(["--doit"])
|
args = parser.parse(["--doit"])
|
||||||
assert args.func_arg is True
|
assert args.func_arg is True
|
||||||
|
|
||||||
def test_drop_short_3(self, parser):
|
def test_drop_short_3(self, parser: parseopt.Parser) -> None:
|
||||||
parser.addoption("--func-arg", "--funcarg", "--doit", action="store_true")
|
parser.addoption("--func-arg", "--funcarg", "--doit", action="store_true")
|
||||||
args = parser.parse(["abcd"])
|
args = parser.parse(["abcd"])
|
||||||
assert args.func_arg is False
|
assert args.func_arg is False
|
||||||
assert args.file_or_dir == ["abcd"]
|
assert args.file_or_dir == ["abcd"]
|
||||||
|
|
||||||
def test_drop_short_help0(self, parser, capsys):
|
def test_drop_short_help0(self, parser: parseopt.Parser, capsys) -> None:
|
||||||
parser.addoption("--func-args", "--doit", help="foo", action="store_true")
|
parser.addoption("--func-args", "--doit", help="foo", action="store_true")
|
||||||
parser.parse([])
|
parser.parse([])
|
||||||
help = parser.optparser.format_help()
|
help = parser.optparser.format_help()
|
||||||
assert "--func-args, --doit foo" in help
|
assert "--func-args, --doit foo" in help
|
||||||
|
|
||||||
# testing would be more helpful with all help generated
|
# testing would be more helpful with all help generated
|
||||||
def test_drop_short_help1(self, parser, capsys):
|
def test_drop_short_help1(self, parser: parseopt.Parser, capsys) -> None:
|
||||||
group = parser.getgroup("general")
|
group = parser.getgroup("general")
|
||||||
group.addoption("--doit", "--func-args", action="store_true", help="foo")
|
group.addoption("--doit", "--func-args", action="store_true", help="foo")
|
||||||
group._addoption(
|
group._addoption(
|
||||||
|
@ -275,7 +272,7 @@ class TestParser:
|
||||||
help = parser.optparser.format_help()
|
help = parser.optparser.format_help()
|
||||||
assert "-doit, --func-args foo" in help
|
assert "-doit, --func-args foo" in help
|
||||||
|
|
||||||
def test_multiple_metavar_help(self, parser):
|
def test_multiple_metavar_help(self, parser: parseopt.Parser) -> None:
|
||||||
"""
|
"""
|
||||||
Help text for options with a metavar tuple should display help
|
Help text for options with a metavar tuple should display help
|
||||||
in the form "--preferences=value1 value2 value3" (#2004).
|
in the form "--preferences=value1 value2 value3" (#2004).
|
||||||
|
@ -290,7 +287,7 @@ class TestParser:
|
||||||
assert "--preferences=value1 value2 value3" in help
|
assert "--preferences=value1 value2 value3" in help
|
||||||
|
|
||||||
|
|
||||||
def test_argcomplete(testdir, monkeypatch):
|
def test_argcomplete(testdir, monkeypatch) -> None:
|
||||||
if not distutils.spawn.find_executable("bash"):
|
if not distutils.spawn.find_executable("bash"):
|
||||||
pytest.skip("bash not available")
|
pytest.skip("bash not available")
|
||||||
script = str(testdir.tmpdir.join("test_argcomplete"))
|
script = str(testdir.tmpdir.join("test_argcomplete"))
|
||||||
|
|
Loading…
Reference in New Issue