Skip to content

gh-103498: argparse.ArgumentParser will always throw instead of exit if exit_on_error=False #103519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2199,10 +2199,12 @@ Exiting methods
raise Exception(f'Exiting because of an error: {message}')
exit(status)

.. method:: ArgumentParser.error(message)
.. method:: ArgumentParser.error(message, action=None)

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


Intermixed parsing
Expand Down
39 changes: 18 additions & 21 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
import warnings

from gettext import gettext as _, ngettext
from typing import Optional as _Optional

SUPPRESS = '==SUPPRESS=='

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

# parse the arguments and exit if there are any errors
if self.exit_on_error:
try:
namespace, args = self._parse_known_args(args, namespace)
except ArgumentError as err:
self.error(str(err))
else:
namespace, args = self._parse_known_args(args, namespace)
namespace, args = self._parse_known_args(args, namespace)

if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
Expand Down Expand Up @@ -1970,7 +1964,7 @@ def take_action(action, argument_strings, option_string=None):
if conflict_action in seen_non_default_actions:
msg = _('not allowed with argument %s')
action_name = _get_action_name(conflict_action)
raise ArgumentError(action, msg % action_name)
self.error(msg % action_name, action=action)

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

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

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

# return the number of arguments matched
return len(match.group(1))
Expand Down Expand Up @@ -2526,7 +2520,7 @@ def _get_value(self, action, arg_string):
type_func = self._registry_get('type', action.type, action.type)
if not callable(type_func):
msg = _('%r is not callable')
raise ArgumentError(action, msg % type_func)
self.error(msg % type_func, action=action)

# convert the value to the appropriate type
try:
Expand All @@ -2535,14 +2529,14 @@ def _get_value(self, action, arg_string):
# ArgumentTypeErrors indicate errors
except ArgumentTypeError as err:
msg = str(err)
raise ArgumentError(action, msg)
self.error(msg, action=action)

# TypeErrors or ValueErrors also indicate errors
except (TypeError, ValueError):
name = getattr(action.type, '__name__', repr(action.type))
args = {'type': name, 'value': arg_string}
msg = _('invalid %(type)s value: %(value)r')
raise ArgumentError(action, msg % args)
self.error(msg % args, action=action)

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

# =======================
# Help-formatting methods
Expand Down Expand Up @@ -2617,15 +2611,18 @@ def exit(self, status=0, message=None):
self._print_message(message, _sys.stderr)
_sys.exit(status)

def error(self, message):
"""error(message: string)

Prints a usage message incorporating the message to stderr and
exits.
def error(self, message: str, action: _Optional[Action] = None):
"""Prints a usage message incorporating the message to stderr and
exits or raises an ArgumentError if self.exit_on_error is False.

If you override this in a subclass, it should not return -- it
should either exit or raise an exception.
"""
if not self.exit_on_error:
raise ArgumentError(action, message)

self.print_usage(_sys.stderr)
# If action is not None, custom formatting will be applied by ArgumentError
message = str(ArgumentError(action, message))
args = {'prog': self.prog, 'message': message}
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
4 changes: 2 additions & 2 deletions Lib/test/libregrtest/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ def __init__(self, **kwargs) -> None:

class _ArgParser(argparse.ArgumentParser):

def error(self, message):
super().error(message + "\nPass -h or --help for complete help.")
def error(self, message, action=None):
super().error(message + "\nPass -h or --help for complete help.", action=action)


def _create_parser():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed the bug that caused :class:`argparse.ArgumentParser` to ignore the
argument ``exit_on_error=False`` and exit instead of throwing
for some errors.