Skip to content

Commit 602f935

Browse files
committed
config: remove PytestArgumentParser in favor of exit_on_error=False
Since Python 3.9 argparse supports `exit_on_error` parameter that makes it just propagate an exception instead of calling `error`: https://docs.python.org/3/library/argparse.html#exit-on-error We can use it to get rid of `PytestArgumentParser`, less cognitive overhead.
1 parent 483cf8a commit 602f935

File tree

1 file changed

+33
-41
lines changed

1 file changed

+33
-41
lines changed

src/_pytest/config/argparsing.py

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from typing import Any
1111
from typing import final
1212
from typing import Literal
13-
from typing import NoReturn
1413

1514
from .exceptions import UsageError
1615
import _pytest._io
@@ -42,7 +41,14 @@ def __init__(
4241

4342
self._processopt = processopt
4443
self.extra_info: dict[str, Any] = {}
45-
self.optparser = PytestArgumentParser(usage, self.extra_info)
44+
self.optparser = argparse.ArgumentParser(
45+
usage=usage,
46+
add_help=False,
47+
exit_on_error=False,
48+
formatter_class=DropShorterLongHelpFormatter,
49+
allow_abbrev=False,
50+
fromfile_prefix_chars="@",
51+
)
4652
anonymous_arggroup = self.optparser.add_argument_group("Custom options")
4753
self._anonymous = OptionGroup(
4854
anonymous_arggroup, "_anonymous", self, _ispytest=True
@@ -117,6 +123,14 @@ def addoption(self, *opts: str, **attrs: Any) -> None:
117123
"""
118124
self._anonymous.addoption(*opts, **attrs)
119125

126+
def _format_usage_error(self, e: argparse.ArgumentError) -> str:
127+
msg = f"{self.prog}: error: {e}"
128+
if self.extra_info:
129+
msg += "\n" + "\n".join(
130+
f" {k}: {v}" for k, v in sorted(self.extra_info.items())
131+
)
132+
return self.optparser.format_usage() + msg
133+
120134
def parse(
121135
self,
122136
args: Sequence[str | os.PathLike[str]],
@@ -135,9 +149,11 @@ def parse(
135149
strargs = [os.fspath(x) for x in args]
136150
if namespace is None:
137151
namespace = argparse.Namespace()
152+
namespace._raise_print_help = True
138153
try:
139-
namespace._raise_print_help = True
140154
return self.optparser.parse_intermixed_args(strargs, namespace=namespace)
155+
except argparse.ArgumentError as e:
156+
raise UsageError(self._format_usage_error(e)) from None
141157
finally:
142158
del namespace._raise_print_help
143159

@@ -165,17 +181,20 @@ def parse_known_and_unknown_args(
165181
arguments, and a list of unknown flag arguments.
166182
"""
167183
strargs = [os.fspath(x) for x in args]
168-
if sys.version_info < (3, 12):
169-
# Older argparse have a bugged parse_known_intermixed_args.
170-
namespace, unknown = self.optparser.parse_known_args(strargs, namespace)
171-
assert namespace is not None
172-
file_or_dir = getattr(namespace, FILE_OR_DIR)
173-
unknown_flags: list[str] = []
174-
for arg in unknown:
175-
(unknown_flags if arg.startswith("-") else file_or_dir).append(arg)
176-
return namespace, unknown_flags
177-
else:
178-
return self.optparser.parse_known_intermixed_args(strargs, namespace)
184+
try:
185+
if sys.version_info < (3, 12):
186+
# Older argparse have a bugged parse_known_intermixed_args.
187+
namespace, unknown = self.optparser.parse_known_args(strargs, namespace)
188+
assert namespace is not None
189+
file_or_dir = getattr(namespace, FILE_OR_DIR)
190+
unknown_flags: list[str] = []
191+
for arg in unknown:
192+
(unknown_flags if arg.startswith("-") else file_or_dir).append(arg)
193+
return namespace, unknown_flags
194+
else:
195+
return self.optparser.parse_known_intermixed_args(strargs, namespace)
196+
except argparse.ArgumentError as e:
197+
raise UsageError(self._format_usage_error(e)) from None
179198

180199
def addini(
181200
self,
@@ -375,33 +394,6 @@ def _addoption_inner(
375394
self.parser.processoption(option)
376395

377396

378-
class PytestArgumentParser(argparse.ArgumentParser):
379-
def __init__(
380-
self,
381-
usage: str | None,
382-
extra_info: dict[str, str],
383-
) -> None:
384-
super().__init__(
385-
usage=usage,
386-
add_help=False,
387-
formatter_class=DropShorterLongHelpFormatter,
388-
allow_abbrev=False,
389-
fromfile_prefix_chars="@",
390-
)
391-
# extra_info is a dict of (param -> value) to display if there's
392-
# an usage error to provide more contextual information to the user.
393-
self.extra_info = extra_info
394-
395-
def error(self, message: str) -> NoReturn:
396-
"""Transform argparse error message into UsageError."""
397-
msg = f"{self.prog}: error: {message}"
398-
if self.extra_info:
399-
msg += "\n" + "\n".join(
400-
f" {k}: {v}" for k, v in sorted(self.extra_info.items())
401-
)
402-
raise UsageError(self.format_usage() + msg)
403-
404-
405397
class DropShorterLongHelpFormatter(argparse.HelpFormatter):
406398
"""Shorten help for long options that differ only in extra hyphens.
407399

0 commit comments

Comments
 (0)