argparsing: export Parser and OptionGroup for typing purposes

`Parser` is used by many plugins and custom hooks. `OptionGroup` is
exposed by the `parser.addgroup` API.

The constructors of both are marked private, they are not meant to be
constructed directly.
This commit is contained in:
Ran Benita 2021-05-23 23:45:49 +03:00
parent c198a7a67e
commit 538b5c2499
11 changed files with 50 additions and 24 deletions

View File

@ -1,7 +1,7 @@
Added :meth:`cache.mkdir() <pytest.Cache.mkdir>`, which is similar to the existing :meth:`cache.makedir() <pytest.Cache.makedir>`, Added :meth:`cache.mkdir() <pytest.Cache.mkdir>`, which is similar to the existing :meth:`cache.makedir() <pytest.Cache.makedir>`,
but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``. but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``.
Added a ``paths`` type to :meth:`parser.addini() <_pytest.config.argparsing.Parser.addini>`, Added a ``paths`` type to :meth:`parser.addini() <pytest.Parser.addini>`,
as in ``parser.addini("mypaths", "my paths", type="paths")``, as in ``parser.addini("mypaths", "my paths", type="paths")``,
which is similar to the existing ``pathlist``, which is similar to the existing ``pathlist``,
but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``. but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``.

View File

@ -6,5 +6,7 @@ Directly constructing the following classes is now deprecated:
- ``_pytest.python.Metafunc`` - ``_pytest.python.Metafunc``
- ``_pytest.runner.CallInfo`` - ``_pytest.runner.CallInfo``
- ``_pytest._code.ExceptionInfo`` - ``_pytest._code.ExceptionInfo``
- ``_pytest.config.argparsing.Parser``
- ``_pytest.config.argparsing.OptionGroup``
These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0.

View File

@ -8,6 +8,8 @@ The newly-exported types are:
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :func:`pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook. - ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :func:`pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
- ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks. - ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
- ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks. - ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks.
- ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :func:`pytest_addoption <pytest.hookspec.pytest_addoption>` hook.
- ``pytest.OptionGroup`` for the :class:`OptionGroup <pytest.OptionGroup>` type returned from the :func:`parser.addgroup <pytest.Parser.getgroup>` method.
Constructing them directly is not supported; they are only meant for use in type annotations. Constructing them directly is not supported; they are only meant for use in type annotations.
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.

View File

@ -1,4 +1,4 @@
Several behaviors of :meth:`Parser.addoption <_pytest.config.argparsing.Parser.addoption>` are now Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
scheduled for removal in pytest 7 (deprecated since pytest 2.4.0): scheduled for removal in pytest 7 (deprecated since pytest 2.4.0):
- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. - ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.

View File

@ -48,7 +48,7 @@ Backward compatibilities in ``Parser.addoption``
.. deprecated:: 2.4 .. deprecated:: 2.4
Several behaviors of :meth:`Parser.addoption <_pytest.config.argparsing.Parser.addoption>` are now Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
scheduled for removal in pytest 7 (deprecated since pytest 2.4.0): scheduled for removal in pytest 7 (deprecated since pytest 2.4.0):
- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. - ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.

View File

@ -889,9 +889,14 @@ Node
Parser Parser
~~~~~~ ~~~~~~
.. autoclass:: _pytest.config.argparsing.Parser() .. autoclass:: pytest.Parser()
:members: :members:
OptionGroup
~~~~~~~~~~~
.. autoclass:: pytest.OptionGroup()
:members:
PytestPluginManager PytestPluginManager
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -909,6 +909,7 @@ class Config:
self._parser = Parser( self._parser = Parser(
usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
processopt=self._processopt, processopt=self._processopt,
_ispytest=True,
) )
self.pluginmanager = pluginmanager self.pluginmanager = pluginmanager
"""The plugin manager handles plugin registration and hook invocation. """The plugin manager handles plugin registration and hook invocation.
@ -1380,8 +1381,8 @@ class Config:
"""Return configuration value from an :ref:`ini file <configfiles>`. """Return configuration value from an :ref:`ini file <configfiles>`.
If the specified name hasn't been registered through a prior If the specified name hasn't been registered through a prior
:py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>` :func:`parser.addini <pytest.Parser.addini>` call (usually from a
call (usually from a plugin), a ValueError is raised. plugin), a ValueError is raised.
""" """
try: try:
return self._inicache[name] return self._inicache[name]

View File

@ -21,6 +21,7 @@ from _pytest.config.exceptions import UsageError
from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT
from _pytest.deprecated import ARGUMENT_TYPE_STR from _pytest.deprecated import ARGUMENT_TYPE_STR
from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE
from _pytest.deprecated import check_ispytest
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import NoReturn from typing import NoReturn
@ -43,8 +44,11 @@ class Parser:
self, self,
usage: Optional[str] = None, usage: Optional[str] = None,
processopt: Optional[Callable[["Argument"], None]] = None, processopt: Optional[Callable[["Argument"], None]] = None,
*,
_ispytest: bool = False,
) -> None: ) -> None:
self._anonymous = OptionGroup("custom options", parser=self) check_ispytest(_ispytest)
self._anonymous = OptionGroup("custom options", parser=self, _ispytest=True)
self._groups: List[OptionGroup] = [] self._groups: List[OptionGroup] = []
self._processopt = processopt self._processopt = processopt
self._usage = usage self._usage = usage
@ -67,14 +71,14 @@ class Parser:
:after: Name of another group, used for ordering --help output. :after: Name of another group, used for ordering --help output.
The returned group object has an ``addoption`` method with the same The returned group object has an ``addoption`` method with the same
signature as :py:func:`parser.addoption signature as :func:`parser.addoption <pytest.Parser.addoption>` but
<_pytest.config.argparsing.Parser.addoption>` but will be shown in the will be shown in the respective group in the output of
respective group in the output of ``pytest. --help``. ``pytest. --help``.
""" """
for group in self._groups: for group in self._groups:
if group.name == name: if group.name == name:
return group return group
group = OptionGroup(name, description, parser=self) group = OptionGroup(name, description, parser=self, _ispytest=True)
i = 0 i = 0
for i, grp in enumerate(self._groups): for i, grp in enumerate(self._groups):
if grp.name == after: if grp.name == after:
@ -334,9 +338,17 @@ class Argument:
class OptionGroup: class OptionGroup:
"""A group of options shown in its own section."""
def __init__( def __init__(
self, name: str, description: str = "", parser: Optional[Parser] = None self,
name: str,
description: str = "",
parser: Optional[Parser] = None,
*,
_ispytest: bool = False,
) -> None: ) -> None:
check_ispytest(_ispytest)
self.name = name self.name = name
self.description = description self.description = description
self.options: List[Argument] = [] self.options: List[Argument] = []
@ -346,9 +358,9 @@ class OptionGroup:
"""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
be suppressed in the help. addoption('--twowords', '--two-words') be suppressed in the help. ``addoption('--twowords', '--two-words')``
results in help showing '--two-words' only, but --twowords gets results in help showing ``--two-words`` only, but ``--twowords`` gets
accepted **and** the automatic destination is in args.twowords. accepted **and** the automatic destination is in ``args.twowords``.
""" """
conflict = set(optnames).intersection( conflict = set(optnames).intersection(
name for opt in self.options for name in opt.names() name for opt in self.options for name in opt.names()

View File

@ -88,11 +88,11 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
files situated at the tests root directory due to how pytest files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`. :ref:`discovers plugins during startup <pluginorder>`.
:param _pytest.config.argparsing.Parser parser: :param pytest.Parser parser:
To add command line options, call To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.argparsing.Parser.addoption>`. :py:func:`parser.addoption(...) <pytest.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...) To add ini-file values call :py:func:`parser.addini(...)
<_pytest.config.argparsing.Parser.addini>`. <pytest.Parser.addini>`.
:param _pytest.config.PytestPluginManager pluginmanager: :param _pytest.config.PytestPluginManager pluginmanager:
pytest plugin manager, which can be used to install :py:func:`hookspec`'s pytest plugin manager, which can be used to install :py:func:`hookspec`'s
@ -193,7 +193,7 @@ def pytest_load_initial_conftests(
:param _pytest.config.Config early_config: The pytest config object. :param _pytest.config.Config early_config: The pytest config object.
:param List[str] args: Arguments passed on the command line. :param List[str] args: Arguments passed on the command line.
:param _pytest.config.argparsing.Parser parser: To add command line options. :param pytest.Parser parser: To add command line options.
""" """

View File

@ -13,6 +13,8 @@ from _pytest.config import hookimpl
from _pytest.config import hookspec from _pytest.config import hookspec
from _pytest.config import main from _pytest.config import main
from _pytest.config import UsageError from _pytest.config import UsageError
from _pytest.config.argparsing import OptionGroup
from _pytest.config.argparsing import Parser
from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.debugging import pytestPDB as __pytestPDB
from _pytest.fixtures import _fillfuncargs from _pytest.fixtures import _fillfuncargs
from _pytest.fixtures import fixture from _pytest.fixtures import fixture
@ -103,8 +105,10 @@ __all__ = [
"Metafunc", "Metafunc",
"Module", "Module",
"MonkeyPatch", "MonkeyPatch",
"OptionGroup",
"Package", "Package",
"param", "param",
"Parser",
"PytestAssertRewriteWarning", "PytestAssertRewriteWarning",
"PytestCacheWarning", "PytestCacheWarning",
"PytestCollectionWarning", "PytestCollectionWarning",

View File

@ -14,12 +14,12 @@ from _pytest.pytester import Pytester
@pytest.fixture @pytest.fixture
def parser() -> parseopt.Parser: def parser() -> parseopt.Parser:
return parseopt.Parser() return parseopt.Parser(_ispytest=True)
class TestParser: class TestParser:
def test_no_help_by_default(self) -> None: def test_no_help_by_default(self) -> None:
parser = parseopt.Parser(usage="xyz") parser = parseopt.Parser(usage="xyz", _ispytest=True)
pytest.raises(UsageError, lambda: parser.parse(["-h"])) pytest.raises(UsageError, lambda: parser.parse(["-h"]))
def test_custom_prog(self, parser: parseopt.Parser) -> None: def test_custom_prog(self, parser: parseopt.Parser) -> None:
@ -90,13 +90,13 @@ class TestParser:
assert groups_names == list("132") assert groups_names == list("132")
def test_group_addoption(self) -> None: def test_group_addoption(self) -> None:
group = parseopt.OptionGroup("hello") group = parseopt.OptionGroup("hello", _ispytest=True)
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) -> None: def test_group_addoption_conflict(self) -> None:
group = parseopt.OptionGroup("hello again") group = parseopt.OptionGroup("hello again", _ispytest=True)
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")
@ -188,7 +188,7 @@ class TestParser:
elif option.type is str: elif option.type is str:
option.default = "world" option.default = "world"
parser = parseopt.Parser(processopt=defaultget) parser = parseopt.Parser(processopt=defaultget, _ispytest=True)
parser.addoption("--this", dest="this", type=int, action="store") parser.addoption("--this", dest="this", type=int, action="store")
parser.addoption("--hello", dest="hello", type=str, action="store") parser.addoption("--hello", dest="hello", type=str, action="store")
parser.addoption("--no", dest="no", action="store_true") parser.addoption("--no", dest="no", action="store_true")