diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 9b2afe3e8b4..1511fc97223 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -18,6 +18,7 @@ import enum from functools import lru_cache import glob +import importlib import importlib.metadata import inspect import os @@ -55,6 +56,7 @@ from _pytest._code.code import TracebackStyle from _pytest._io import TerminalWriter from _pytest.compat import assert_never +from _pytest.compat import NOTSET from _pytest.config.argparsing import Argument from _pytest.config.argparsing import FILE_OR_DIR from _pytest.config.argparsing import Parser @@ -873,7 +875,7 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No return try: - __import__(importspec) + mod = importlib.import_module(importspec) except ImportError as e: raise ImportError( f'Error importing plugin "{modname}": {e.args[0]}' @@ -882,7 +884,6 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No except Skipped as e: self.skipped_plugins.append((modname, e.msg or "")) else: - mod = sys.modules[importspec] self.register(mod, modname) @@ -907,14 +908,6 @@ def _get_plugin_specs_as_list( ) -class Notset: - def __repr__(self): - return "" - - -notset = Notset() - - def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: """Given an iterable of file names in a source distribution, return the "names" that should be marked for assertion rewrite. @@ -1090,7 +1083,6 @@ def __init__( self.trace = self.pluginmanager.trace.root.get("config") self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] self._inicache: dict[str, Any] = {} - self._opt2dest: dict[str, str] = {} self._cleanup_stack = contextlib.ExitStack() self.pluginmanager.register(self, "pytestconfig") self._configured = False @@ -1215,12 +1207,8 @@ def fromdictargs(cls, option_dict: Mapping[str, Any], args: list[str]) -> Config return config def _processopt(self, opt: Argument) -> None: - for name in opt._short_opts + opt._long_opts: - self._opt2dest[name] = opt.dest - - if hasattr(opt, "default"): - if not hasattr(self.option, opt.dest): - setattr(self.option, opt.dest, opt.default) + if not hasattr(self.option, opt.dest): + setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config: Config) -> None: @@ -1376,17 +1364,11 @@ def pytest_collection(self) -> Generator[None, object, object]: def _checkversion(self) -> None: import pytest - minver_ini_value = self.inicfg.get("minversion", None) - minver = minver_ini_value.value if minver_ini_value is not None else None + minver = self.getini("minversion") if minver: # Imported lazily to improve start-up time. from packaging.version import Version - if not isinstance(minver, str): - raise pytest.UsageError( - f"{self.inipath}: 'minversion' must be a single value" - ) - if Version(minver) > Version(pytest.__version__): raise pytest.UsageError( f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" @@ -1840,7 +1822,7 @@ def _getconftest_pathlist( values.append(relroot) return values - def getoption(self, name: str, default: Any = notset, skip: bool = False): + def getoption(self, name: str, default: Any = NOTSET, skip: bool = False): """Return command line option value. :param name: Name of the option. You may also specify @@ -1850,14 +1832,14 @@ def getoption(self, name: str, default: Any = notset, skip: bool = False): :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. Note that even if ``True``, if a default was specified it will be returned instead of a skip. """ - name = self._opt2dest.get(name, name) + name = self._parser._opt2dest.get(name, name) try: val = getattr(self.option, name) if val is None and skip: raise AttributeError(name) return val except AttributeError as e: - if default is not notset: + if default is not NOTSET: return default if skip: import pytest @@ -2134,7 +2116,7 @@ def _resolve_warning_category(category: str) -> type[Warning]: klass = category else: module, _, klass = category.rpartition(".") - m = __import__(module, None, None, [klass]) + m = importlib.import_module(module) cat = getattr(m, klass) if not issubclass(cat, Warning): raise UsageError(f"{cat} is not a Warning subclass") diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 995408800a8..0901941a6dc 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -3,10 +3,10 @@ import argparse from collections.abc import Callable -from collections.abc import Mapping from collections.abc import Sequence import os import sys +import textwrap from typing import Any from typing import final from typing import Literal @@ -14,20 +14,13 @@ from .exceptions import UsageError import _pytest._io +from _pytest.compat import NOTSET from _pytest.deprecated import check_ispytest FILE_OR_DIR = "file_or_dir" -class NotSet: - def __repr__(self) -> str: - return "" - - -NOT_SET = NotSet() - - @final class Parser: """Parser for command line arguments and config-file values. @@ -49,12 +42,14 @@ def __init__( self._processopt = processopt self.extra_info: dict[str, Any] = {} - self.optparser = PytestArgumentParser(self, usage, self.extra_info) + self.optparser = PytestArgumentParser(usage, self.extra_info) anonymous_arggroup = self.optparser.add_argument_group("Custom options") self._anonymous = OptionGroup( anonymous_arggroup, "_anonymous", self, _ispytest=True ) self._groups = [self._anonymous] + # Maps option strings -> dest, e.g. "-V" and "--version" to "version". + self._opt2dest: dict[str, str] = {} file_or_dir_arg = self.optparser.add_argument(FILE_OR_DIR, nargs="*") file_or_dir_arg.completer = filescompleter # type: ignore @@ -190,7 +185,7 @@ def addini( "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" ] | None = None, - default: Any = NOT_SET, + default: Any = NOTSET, *, aliases: Sequence[str] = (), ) -> None: @@ -250,7 +245,7 @@ def addini( ) if type is None: type = "string" - if default is NOT_SET: + if default is NOTSET: default = get_ini_default_for_type(type) self._inidict[name] = (help, type, default) @@ -286,109 +281,37 @@ def get_ini_default_for_type( return "" -class ArgumentError(Exception): - """Raised if an Argument instance is created with invalid or - inconsistent arguments.""" +class Argument: + """An option defined in an OptionGroup.""" - def __init__(self, msg: str, option: Argument | str) -> None: - self.msg = msg - self.option_id = str(option) + def __init__(self, action: argparse.Action) -> None: + self._action = action - def __str__(self) -> str: - if self.option_id: - return f"option {self.option_id}: {self.msg}" - else: - return self.msg + def attrs(self) -> dict[str, Any]: + return self._action.__dict__ + def names(self) -> Sequence[str]: + return self._action.option_strings -class Argument: - """Class that mimics the necessary behaviour of optparse.Option. - - It's currently a least effort implementation and ignoring choices - and integer prefixes. + @property + def dest(self) -> str: + return self._action.dest - https://docs.python.org/3/library/optparse.html#optparse-standard-option-types - """ + @property + def default(self) -> Any: + return self._action.default - def __init__(self, *names: str, **attrs: Any) -> None: - """Store params in private vars for use in add_argument.""" - self._attrs = attrs - self._short_opts: list[str] = [] - self._long_opts: list[str] = [] - try: - self.type = attrs["type"] - except KeyError: - pass - try: - # Attribute existence is tested in Config._processopt. - self.default = attrs["default"] - except KeyError: - pass - self._set_opt_strings(names) - dest: str | None = 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. - for attr in ("default", "dest", "help", self.dest): - try: - self._attrs[attr] = getattr(self, attr) - except AttributeError: - pass - 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( - f"invalid option string {opt!r}: " - "must be at least two characters long", - self, - ) - elif len(opt) == 2: - if not (opt[0] == "-" and opt[1] != "-"): - raise ArgumentError( - f"invalid short option string {opt!r}: " - "must be of the form -x, (x any non-dash char)", - self, - ) - self._short_opts.append(opt) - else: - if not (opt[0:2] == "--" and opt[2] != "-"): - raise ArgumentError( - f"invalid long option string {opt!r}: " - "must start with --, followed by non-dash", - self, - ) - self._long_opts.append(opt) + @property + def type(self) -> Any | None: + return self._action.type 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 += ["opts: " + repr(self.names())] args += ["dest: " + repr(self.dest)] - if hasattr(self, "type"): + if self._action.type: args += ["type: " + repr(self.type)] - if hasattr(self, "default"): - args += ["default: " + repr(self.default)] + args += ["default: " + repr(self.default)] return "Argument({})".format(", ".join(args)) @@ -418,6 +341,7 @@ def addoption(self, *opts: str, **attrs: Any) -> None: :param opts: Option names, can be short or long options. + Note that lower-case short options (e.g. `-x`) are reserved. :param attrs: Same attributes as the argparse library's :meth:`add_argument() ` function accepts. @@ -427,34 +351,36 @@ def addoption(self, *opts: str, **attrs: Any) -> None: ) if conflict: raise ValueError(f"option names {conflict} already added") - option = Argument(*opts, **attrs) - self._addoption_instance(option, shortupper=False) + self._addoption_inner(opts, attrs, allow_reserved=False) def _addoption(self, *opts: str, **attrs: Any) -> None: - option = Argument(*opts, **attrs) - self._addoption_instance(option, shortupper=True) + """Like addoption(), but also allows registering short lower case options (e.g. -x), + which are reserved for pytest core.""" + self._addoption_inner(opts, attrs, allow_reserved=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") + def _addoption_inner( + self, opts: tuple[str, ...], attrs: dict[str, Any], allow_reserved: bool + ) -> None: + if not allow_reserved: + for opt in opts: + if len(opt) >= 2 and opt[0] == "-" and opt[1].islower(): + raise ValueError("lowercase short options are reserved") + action = self._arggroup.add_argument(*opts, **attrs) + option = Argument(action) + self.options.append(option) if self.parser: + for name in option.names(): + self.parser._opt2dest[name] = option.dest self.parser.processoption(option) - self._arggroup.add_argument(*option.names(), **option.attrs()) - self.options.append(option) - class PytestArgumentParser(argparse.ArgumentParser): def __init__( self, - parser: Parser, usage: str | None, extra_info: dict[str, str], ) -> None: - self._parser = parser super().__init__( usage=usage, add_help=False, @@ -468,6 +394,8 @@ def __init__( def error(self, message: str) -> NoReturn: """Transform argparse error message into UsageError.""" + # TODO(py313): Replace with `exit_on_error=False`. Note that while it + # was added in Python 3.9, it was broken until 3.13 (cpython#121018). msg = f"{self.prog}: error: {message}" if self.extra_info: msg += "\n" + "\n".join( @@ -481,7 +409,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): - 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: @@ -494,23 +421,18 @@ def _format_action_invocation(self, action: argparse.Action) -> str: orgstr = super()._format_action_invocation(action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res: str | None = 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 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( - f'long optional argument without "--": [{option}]', option - ) + assert option.startswith("--"), ( + f'long optional argument without "--": [{option}]' + ) xxoption = option[2:] shortened = xxoption.replace("-", "") if shortened not in short_long or len(short_long[shortened]) < len( @@ -524,17 +446,13 @@ def _format_action_invocation(self, action: argparse.Action) -> str: 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 + return ", ".join(return_list) - def _split_lines(self, text, width): + def _split_lines(self, text: str, width: int) -> list[str]: """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)) diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index de1b2688f76..cf9d0fdd247 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -8,6 +8,7 @@ from collections.abc import Callable from collections.abc import Generator import functools +import importlib import sys import types from typing import Any @@ -123,8 +124,7 @@ def _import_pdb_cls(cls, capman: CaptureManager | None): modname, classname = usepdb_cls try: - __import__(modname) - mod = sys.modules[modname] + mod = importlib.import_module(modname) # Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp). parts = classname.split(".") diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 6a22c9f58ac..fdba02b35f4 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -59,8 +59,8 @@ def __call__( def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( - "--version", "-V", + "--version", action="count", default=0, dest="version", diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 841d7811fdd..56c407ab371 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -19,11 +19,11 @@ from .structures import MarkDecorator from .structures import MarkGenerator from .structures import ParameterSet +from _pytest.compat import NOTSET from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config import UsageError -from _pytest.config.argparsing import NOT_SET from _pytest.config.argparsing import Parser from _pytest.stash import StashKey @@ -247,7 +247,7 @@ def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: return False for mark in matches: # pylint: disable=consider-using-any-or-all - if all(mark.kwargs.get(k, NOT_SET) == v for k, v in kwargs.items()): + if all(mark.kwargs.get(k, NOTSET) == v for k, v in kwargs.items()): return True return False diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 07cc3fc4b0f..6c033f36fda 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -7,6 +7,7 @@ from collections.abc import Mapping from collections.abc import MutableMapping from contextlib import contextmanager +import importlib import os from pathlib import Path import re @@ -17,6 +18,8 @@ from typing import TypeVar import warnings +from _pytest.compat import NOTSET +from _pytest.compat import NotSetType from _pytest.deprecated import MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES from _pytest.fixtures import fixture from _pytest.warning_types import PytestWarning @@ -64,7 +67,7 @@ def resolve(name: str) -> object: parts = name.split(".") used = parts.pop(0) - found: object = __import__(used) + found: object = importlib.import_module(used) for part in parts: used += "." + part try: @@ -76,7 +79,7 @@ def resolve(name: str) -> object: # We use explicit un-nesting of the handling block in order # to avoid nested exceptions. try: - __import__(used) + importlib.import_module(used) except ImportError as ex: expected = str(ex).split()[-1] if expected == used: @@ -107,14 +110,6 @@ def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: return attr, target -class Notset: - def __repr__(self) -> str: - return "" - - -notset = Notset() - - @final class MonkeyPatch: """Helper to conveniently monkeypatch attributes/items/environment @@ -167,7 +162,7 @@ def setattr( self, target: str, name: object, - value: Notset = ..., + value: NotSetType = ..., raising: bool = ..., ) -> None: ... @@ -184,7 +179,7 @@ def setattr( self, target: str | object, name: object | str, - value: object = notset, + value: object = NOTSET, raising: bool = True, ) -> None: """ @@ -225,7 +220,7 @@ def setattr( __tracebackhide__ = True import inspect - if isinstance(value, Notset): + if value is NOTSET: if not isinstance(target, str): raise TypeError( "use setattr(target, name, value) or " @@ -242,20 +237,20 @@ def setattr( "import string" ) - oldval = getattr(target, name, notset) - if raising and oldval is notset: + oldval = getattr(target, name, NOTSET) + if raising and oldval is NOTSET: raise AttributeError(f"{target!r} has no attribute {name!r}") # avoid class descriptors like staticmethod/classmethod if inspect.isclass(target): - oldval = target.__dict__.get(name, notset) + oldval = target.__dict__.get(name, NOTSET) self._setattr.append((target, name, oldval)) setattr(target, name, value) def delattr( self, target: object | str, - name: str | Notset = notset, + name: str | NotSetType = NOTSET, raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. @@ -270,7 +265,7 @@ def delattr( __tracebackhide__ = True import inspect - if isinstance(name, Notset): + if name is NOTSET: if not isinstance(target, str): raise TypeError( "use delattr(target, name) or " @@ -283,16 +278,16 @@ def delattr( if raising: raise AttributeError(name) else: - oldval = getattr(target, name, notset) + oldval = getattr(target, name, NOTSET) # Avoid class descriptors like staticmethod/classmethod. if inspect.isclass(target): - oldval = target.__dict__.get(name, notset) + oldval = target.__dict__.get(name, NOTSET) self._setattr.append((target, name, oldval)) delattr(target, name) def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None: """Set dictionary entry ``name`` to value.""" - self._setitem.append((dic, name, dic.get(name, notset))) + self._setitem.append((dic, name, dic.get(name, NOTSET))) # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict dic[name] = value # type: ignore[index] @@ -306,7 +301,7 @@ def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: if raising: raise KeyError(name) else: - self._setitem.append((dic, name, dic.get(name, notset))) + self._setitem.append((dic, name, dic.get(name, NOTSET))) # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict del dic[name] # type: ignore[attr-defined] @@ -410,13 +405,13 @@ def undo(self) -> None: Prefer to use :meth:`context() ` instead. """ for obj, name, value in reversed(self._setattr): - if value is not notset: + if value is not NOTSET: setattr(obj, name, value) else: delattr(obj, name) self._setattr[:] = [] for dictionary, key, value in reversed(self._setitem): - if value is notset: + if value is NOTSET: try: # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict del dictionary[key] # type: ignore[attr-defined] diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 766be95c0f7..09d0a6eccea 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -3,6 +3,7 @@ from __future__ import annotations +import importlib import sys from typing import Any from typing import ClassVar @@ -268,7 +269,7 @@ def importorskip( warnings.simplefilter("ignore") try: - __import__(modname) + importlib.import_module(modname) except exc_type as exc: # Do not raise or issue warnings inside the catch_warnings() block. if reason is None: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index b9384008483..11cc8a7217f 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -129,9 +129,9 @@ class DummyEntryPoint: group: str = "pytest11" def load(self): - __import__(self.module) + mod = importlib.import_module(self.module) loaded.append(self.name) - return sys.modules[self.module] + return mod entry_points = [ DummyEntryPoint("myplugin1", "mytestplugin1_module"), diff --git a/testing/test_config.py b/testing/test_config.py index 98555e04452..65bf94ae19b 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -600,8 +600,7 @@ class DummyEntryPoint: group: str = "pytest11" def load(self): - __import__(self.module) - return sys.modules[self.module] + return importlib.import_module(self.module) entry_points = [ DummyEntryPoint("myplugin1", "myplugin1_module"), diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 30370d3d673..4b721cb96f6 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -34,42 +34,44 @@ def test_custom_prog(self, parser: parseopt.Parser) -> None: assert parser.optparser.prog == "custom-prog" def test_argument(self) -> None: - with pytest.raises(parseopt.ArgumentError): - # need a short or long option - argument = parseopt.Argument() - argument = parseopt.Argument("-t") - assert argument._short_opts == ["-t"] - assert argument._long_opts == [] - assert argument.dest == "t" - argument = parseopt.Argument("-t", "--test") - assert argument._short_opts == ["-t"] - assert argument._long_opts == ["--test"] - assert argument.dest == "test" - argument = parseopt.Argument("-t", "--test", dest="abc") + parser = argparse.ArgumentParser() + + action = parser.add_argument("-a") + argument = parseopt.Argument(action) + assert argument.names() == ["-a"] + assert argument.dest == "a" + + action = parser.add_argument("-b", "--boop") + argument = parseopt.Argument(action) + assert argument.names() == ["-b", "--boop"] + assert argument.dest == "boop" + + action = parser.add_argument("-c", "--coop", dest="abc") + argument = parseopt.Argument(action) assert argument.dest == "abc" - assert str(argument) == ( - "Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')" + assert ( + str(argument) + == "Argument(opts: ['-c', '--coop'], dest: 'abc', default: None)" ) def test_argument_type(self) -> None: - argument = parseopt.Argument("-t", dest="abc", type=int) + parser = argparse.ArgumentParser() + + action = parser.add_argument("-a", dest="aa", type=int) + argument = parseopt.Argument(action) assert argument.type is int - argument = parseopt.Argument("-t", dest="abc", type=str) + + action = parser.add_argument("-b", dest="bb", type=str) + argument = parseopt.Argument(action) assert argument.type is str - argument = parseopt.Argument("-t", dest="abc", type=float) + + action = parser.add_argument("-c", dest="cc", type=float) + argument = parseopt.Argument(action) assert argument.type is float - argument = parseopt.Argument( - "-t", dest="abc", type=str, choices=["red", "blue"] - ) - assert argument.type is str - def test_argument_processopt(self) -> None: - argument = parseopt.Argument("-t", type=int) - argument.default = 42 - argument.dest = "abc" - res = argument.attrs() - assert res["default"] == 42 - assert res["dest"] == "abc" + action = parser.add_argument("-d", dest="dd", type=str, choices=["red", "blue"]) + argument = parseopt.Argument(action) + assert argument.type is str def test_group_add_and_get(self, parser: parseopt.Parser) -> None: group = parser.getgroup("hello") @@ -191,24 +193,6 @@ def test_parse_split_positional_arguments(self, parser: parseopt.Parser) -> None assert args.R is True assert args.S is False - def test_parse_defaultgetter(self) -> None: - def defaultget(option): - if not hasattr(option, "type"): - return - if option.type is int: - option.default = 42 - elif option.type is str: - option.default = "world" - - parser = parseopt.Parser(processopt=defaultget, _ispytest=True) - parser.addoption("--this", dest="this", type=int, action="store") - parser.addoption("--hello", dest="hello", type=str, action="store") - parser.addoption("--no", dest="no", action="store_true") - option = parser.parse([]) - assert option.hello == "world" - assert option.this == 42 - assert option.no is False - def test_drop_short_helper(self) -> None: parser = argparse.ArgumentParser( formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 0880c355557..1dec3c6ec78 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -964,7 +964,7 @@ def test_demo(): ) # unit test - __import__(name) # import standard library + importlib.import_module(name) # import standard library import_path( # import user files file_path,