Skip to content

Commit ec4f0b7

Browse files
committed
gh-103498: argparse.ArgumentParser will always throw instead of exit if exit_on_error=False
1 parent 59e0de4 commit ec4f0b7

File tree

4 files changed

+26
-25
lines changed

4 files changed

+26
-25
lines changed

Doc/library/argparse.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,10 +2199,12 @@ Exiting methods
21992199
raise Exception(f'Exiting because of an error: {message}')
22002200
exit(status)
22012201

2202-
.. method:: ArgumentParser.error(message)
2202+
.. method:: ArgumentParser.error(message, action=None)
22032203

2204-
This method prints a usage message including the *message* to the
2205-
standard error and terminates the program with a status code of 2.
2204+
This method prints a usage message including the *message* and the *action*
2205+
causing the error (if it is set) to the standard error and terminates
2206+
the program with a status code of 2, if ``exit_on_error`` parameter is
2207+
set to ``True``. Otherwise, it raises an :exc:`ArgumentError`.
22062208

22072209

22082210
Intermixed parsing

Lib/argparse.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
import warnings
9393

9494
from gettext import gettext as _, ngettext
95+
from typing import Optional as _Optional
9596

9697
SUPPRESS = '==SUPPRESS=='
9798

@@ -1896,14 +1897,7 @@ def parse_known_args(self, args=None, namespace=None):
18961897
if not hasattr(namespace, dest):
18971898
setattr(namespace, dest, self._defaults[dest])
18981899

1899-
# parse the arguments and exit if there are any errors
1900-
if self.exit_on_error:
1901-
try:
1902-
namespace, args = self._parse_known_args(args, namespace)
1903-
except ArgumentError as err:
1904-
self.error(str(err))
1905-
else:
1906-
namespace, args = self._parse_known_args(args, namespace)
1900+
namespace, args = self._parse_known_args(args, namespace)
19071901

19081902
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
19091903
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
@@ -1970,7 +1964,7 @@ def take_action(action, argument_strings, option_string=None):
19701964
if conflict_action in seen_non_default_actions:
19711965
msg = _('not allowed with argument %s')
19721966
action_name = _get_action_name(conflict_action)
1973-
raise ArgumentError(action, msg % action_name)
1967+
self.error(msg % action_name, action=action)
19741968

19751969
# take the action if we didn't receive a SUPPRESS value
19761970
# (e.g. from a default)
@@ -2019,7 +2013,7 @@ def consume_optional(start_index):
20192013
explicit_arg = new_explicit_arg
20202014
else:
20212015
msg = _('ignored explicit argument %r')
2022-
raise ArgumentError(action, msg % explicit_arg)
2016+
self.error(msg % explicit_arg, action=action)
20232017

20242018
# if the action expect exactly one argument, we've
20252019
# successfully matched the option; exit the loop
@@ -2033,7 +2027,7 @@ def consume_optional(start_index):
20332027
# explicit argument
20342028
else:
20352029
msg = _('ignored explicit argument %r')
2036-
raise ArgumentError(action, msg % explicit_arg)
2030+
self.error(msg % explicit_arg, action=action)
20372031

20382032
# if there is no explicit argument, try to match the
20392033
# optional's string arguments with the following strings
@@ -2207,7 +2201,7 @@ def _match_argument(self, action, arg_strings_pattern):
22072201
msg = ngettext('expected %s argument',
22082202
'expected %s arguments',
22092203
action.nargs) % action.nargs
2210-
raise ArgumentError(action, msg)
2204+
self.error(msg, action=action)
22112205

22122206
# return the number of arguments matched
22132207
return len(match.group(1))
@@ -2526,7 +2520,7 @@ def _get_value(self, action, arg_string):
25262520
type_func = self._registry_get('type', action.type, action.type)
25272521
if not callable(type_func):
25282522
msg = _('%r is not callable')
2529-
raise ArgumentError(action, msg % type_func)
2523+
self.error(msg % type_func, action=action)
25302524

25312525
# convert the value to the appropriate type
25322526
try:
@@ -2535,14 +2529,14 @@ def _get_value(self, action, arg_string):
25352529
# ArgumentTypeErrors indicate errors
25362530
except ArgumentTypeError as err:
25372531
msg = str(err)
2538-
raise ArgumentError(action, msg)
2532+
self.error(msg, action=action)
25392533

25402534
# TypeErrors or ValueErrors also indicate errors
25412535
except (TypeError, ValueError):
25422536
name = getattr(action.type, '__name__', repr(action.type))
25432537
args = {'type': name, 'value': arg_string}
25442538
msg = _('invalid %(type)s value: %(value)r')
2545-
raise ArgumentError(action, msg % args)
2539+
self.error(msg % args, action=action)
25462540

25472541
# return the converted value
25482542
return result
@@ -2553,7 +2547,7 @@ def _check_value(self, action, value):
25532547
args = {'value': value,
25542548
'choices': ', '.join(map(repr, action.choices))}
25552549
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
2556-
raise ArgumentError(action, msg % args)
2550+
self.error(msg % args, action=action)
25572551

25582552
# =======================
25592553
# Help-formatting methods
@@ -2617,15 +2611,18 @@ def exit(self, status=0, message=None):
26172611
self._print_message(message, _sys.stderr)
26182612
_sys.exit(status)
26192613

2620-
def error(self, message):
2621-
"""error(message: string)
2622-
2623-
Prints a usage message incorporating the message to stderr and
2624-
exits.
2614+
def error(self, message: str, action: _Optional[Action] = None):
2615+
"""Prints a usage message incorporating the message to stderr and
2616+
exits or raises an ArgumentError if self.exit_on_error is False.
26252617
26262618
If you override this in a subclass, it should not return -- it
26272619
should either exit or raise an exception.
26282620
"""
2621+
if not self.exit_on_error:
2622+
raise ArgumentError(action, message)
2623+
26292624
self.print_usage(_sys.stderr)
2625+
# If action is not None, custom formatting will be applied by ArgumentError
2626+
message = str(ArgumentError(action, message))
26302627
args = {'prog': self.prog, 'message': message}
26312628
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)

Lib/test/test_argparse.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5604,7 +5604,6 @@ def test_exit_on_error_with_bad_args(self):
56045604
with self.assertRaises(argparse.ArgumentError):
56055605
self.parser.parse_args('--integers a'.split())
56065606

5607-
56085607
def tearDownModule():
56095608
# Remove global references to avoid looking like we have refleaks.
56105609
RFile.seen = {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed the bug that caused :class:`argparse.ArgumentParser` to ignore the
2+
argument ``exit_on_error=False`` and exit instead of throwing
3+
for some errors.

0 commit comments

Comments
 (0)