mirror of
https://github.com/python/cpython.git
synced 2025-09-26 10:19:53 +00:00
gh-121018: Fix more cases of exiting in argparse when exit_on_error=False (GH-121056)
* parse_intermixed_args() now raises ArgumentError instead of calling error() if exit_on_error is false. * Internal code now always raises ArgumentError instead of calling error(). It is then caught at the higher level and error() is called if exit_on_error is true.
This commit is contained in:
parent
43709d5d54
commit
81a654a342
3 changed files with 59 additions and 16 deletions
|
@ -1791,7 +1791,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
# ==================================
|
# ==================================
|
||||||
def add_subparsers(self, **kwargs):
|
def add_subparsers(self, **kwargs):
|
||||||
if self._subparsers is not None:
|
if self._subparsers is not None:
|
||||||
self.error(_('cannot have multiple subparser arguments'))
|
raise ArgumentError(None, _('cannot have multiple subparser arguments'))
|
||||||
|
|
||||||
# add the parser class to the arguments if it's not present
|
# add the parser class to the arguments if it's not present
|
||||||
kwargs.setdefault('parser_class', type(self))
|
kwargs.setdefault('parser_class', type(self))
|
||||||
|
@ -1846,7 +1846,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
msg = _('unrecognized arguments: %s') % ' '.join(argv)
|
msg = _('unrecognized arguments: %s') % ' '.join(argv)
|
||||||
if self.exit_on_error:
|
if self.exit_on_error:
|
||||||
self.error(msg)
|
self.error(msg)
|
||||||
raise ArgumentError(None, msg)
|
else:
|
||||||
|
raise ArgumentError(None, msg)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_known_args(self, args=None, namespace=None):
|
def parse_known_args(self, args=None, namespace=None):
|
||||||
|
@ -2135,7 +2136,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
self._get_value(action, action.default))
|
self._get_value(action, action.default))
|
||||||
|
|
||||||
if required_actions:
|
if required_actions:
|
||||||
self.error(_('the following arguments are required: %s') %
|
raise ArgumentError(None, _('the following arguments are required: %s') %
|
||||||
', '.join(required_actions))
|
', '.join(required_actions))
|
||||||
|
|
||||||
# make sure all required groups had one option present
|
# make sure all required groups had one option present
|
||||||
|
@ -2151,7 +2152,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
for action in group._group_actions
|
for action in group._group_actions
|
||||||
if action.help is not SUPPRESS]
|
if action.help is not SUPPRESS]
|
||||||
msg = _('one of the arguments %s is required')
|
msg = _('one of the arguments %s is required')
|
||||||
self.error(msg % ' '.join(names))
|
raise ArgumentError(None, msg % ' '.join(names))
|
||||||
|
|
||||||
# return the updated namespace and the extra arguments
|
# return the updated namespace and the extra arguments
|
||||||
return namespace, extras
|
return namespace, extras
|
||||||
|
@ -2178,7 +2179,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
arg_strings = self._read_args_from_files(arg_strings)
|
arg_strings = self._read_args_from_files(arg_strings)
|
||||||
new_arg_strings.extend(arg_strings)
|
new_arg_strings.extend(arg_strings)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
self.error(str(err))
|
raise ArgumentError(None, str(err))
|
||||||
|
|
||||||
# return the modified argument list
|
# return the modified argument list
|
||||||
return new_arg_strings
|
return new_arg_strings
|
||||||
|
@ -2258,7 +2259,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
for action, option_string, sep, explicit_arg in option_tuples])
|
for action, option_string, sep, explicit_arg in option_tuples])
|
||||||
args = {'option': arg_string, 'matches': options}
|
args = {'option': arg_string, 'matches': options}
|
||||||
msg = _('ambiguous option: %(option)s could match %(matches)s')
|
msg = _('ambiguous option: %(option)s could match %(matches)s')
|
||||||
self.error(msg % args)
|
raise ArgumentError(None, msg % args)
|
||||||
|
|
||||||
# if exactly one action matched, this segmentation is good,
|
# if exactly one action matched, this segmentation is good,
|
||||||
# so return the parsed action
|
# so return the parsed action
|
||||||
|
@ -2318,7 +2319,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
|
|
||||||
# shouldn't ever get here
|
# shouldn't ever get here
|
||||||
else:
|
else:
|
||||||
self.error(_('unexpected option string: %s') % option_string)
|
raise ArgumentError(None, _('unexpected option string: %s') % option_string)
|
||||||
|
|
||||||
# return the collected option tuples
|
# return the collected option tuples
|
||||||
return result
|
return result
|
||||||
|
@ -2375,8 +2376,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
def parse_intermixed_args(self, args=None, namespace=None):
|
def parse_intermixed_args(self, args=None, namespace=None):
|
||||||
args, argv = self.parse_known_intermixed_args(args, namespace)
|
args, argv = self.parse_known_intermixed_args(args, namespace)
|
||||||
if argv:
|
if argv:
|
||||||
msg = _('unrecognized arguments: %s')
|
msg = _('unrecognized arguments: %s') % ' '.join(argv)
|
||||||
self.error(msg % ' '.join(argv))
|
if self.exit_on_error:
|
||||||
|
self.error(msg)
|
||||||
|
else:
|
||||||
|
raise ArgumentError(None, msg)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_known_intermixed_args(self, args=None, namespace=None):
|
def parse_known_intermixed_args(self, args=None, namespace=None):
|
||||||
|
|
|
@ -2147,7 +2147,9 @@ class TestAddSubparsers(TestCase):
|
||||||
else:
|
else:
|
||||||
subparsers_kwargs['help'] = 'command help'
|
subparsers_kwargs['help'] = 'command help'
|
||||||
subparsers = parser.add_subparsers(**subparsers_kwargs)
|
subparsers = parser.add_subparsers(**subparsers_kwargs)
|
||||||
self.assertArgumentParserError(parser.add_subparsers)
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
|
'cannot have multiple subparser arguments',
|
||||||
|
parser.add_subparsers)
|
||||||
|
|
||||||
# add first sub-parser
|
# add first sub-parser
|
||||||
parser1_kwargs = dict(description='1 description')
|
parser1_kwargs = dict(description='1 description')
|
||||||
|
@ -6042,7 +6044,8 @@ class TestWrappingMetavar(TestCase):
|
||||||
class TestExitOnError(TestCase):
|
class TestExitOnError(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.parser = argparse.ArgumentParser(exit_on_error=False)
|
self.parser = argparse.ArgumentParser(exit_on_error=False,
|
||||||
|
fromfile_prefix_chars='@')
|
||||||
self.parser.add_argument('--integers', metavar='N', type=int)
|
self.parser.add_argument('--integers', metavar='N', type=int)
|
||||||
|
|
||||||
def test_exit_on_error_with_good_args(self):
|
def test_exit_on_error_with_good_args(self):
|
||||||
|
@ -6053,9 +6056,44 @@ class TestExitOnError(TestCase):
|
||||||
with self.assertRaises(argparse.ArgumentError):
|
with self.assertRaises(argparse.ArgumentError):
|
||||||
self.parser.parse_args('--integers a'.split())
|
self.parser.parse_args('--integers a'.split())
|
||||||
|
|
||||||
def test_exit_on_error_with_unrecognized_args(self):
|
def test_unrecognized_args(self):
|
||||||
with self.assertRaises(argparse.ArgumentError):
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
self.parser.parse_args('--foo bar'.split())
|
'unrecognized arguments: --foo bar',
|
||||||
|
self.parser.parse_args, '--foo bar'.split())
|
||||||
|
|
||||||
|
def test_unrecognized_intermixed_args(self):
|
||||||
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
|
'unrecognized arguments: --foo bar',
|
||||||
|
self.parser.parse_intermixed_args, '--foo bar'.split())
|
||||||
|
|
||||||
|
def test_required_args(self):
|
||||||
|
self.parser.add_argument('bar')
|
||||||
|
self.parser.add_argument('baz')
|
||||||
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
|
'the following arguments are required: bar, baz',
|
||||||
|
self.parser.parse_args, [])
|
||||||
|
|
||||||
|
def test_required_mutually_exclusive_args(self):
|
||||||
|
group = self.parser.add_mutually_exclusive_group(required=True)
|
||||||
|
group.add_argument('--bar')
|
||||||
|
group.add_argument('--baz')
|
||||||
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
|
'one of the arguments --bar --baz is required',
|
||||||
|
self.parser.parse_args, [])
|
||||||
|
|
||||||
|
def test_ambiguous_option(self):
|
||||||
|
self.parser.add_argument('--foobaz')
|
||||||
|
self.parser.add_argument('--fooble', action='store_true')
|
||||||
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
|
"ambiguous option: --foob could match --foobaz, --fooble",
|
||||||
|
self.parser.parse_args, ['--foob'])
|
||||||
|
|
||||||
|
def test_os_error(self):
|
||||||
|
self.parser.add_argument('file')
|
||||||
|
self.assertRaisesRegex(argparse.ArgumentError,
|
||||||
|
"No such file or directory: 'no-such-file'",
|
||||||
|
self.parser.parse_args, ['@no-such-file'])
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule():
|
def tearDownModule():
|
||||||
# Remove global references to avoid looking like we have refleaks.
|
# Remove global references to avoid looking like we have refleaks.
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
Fixed an issue where :func:`argparse.ArgumentParser.parses_args` did not honor ``exit_on_error=False`` when given unrecognized arguments.
|
Fixed issues where :meth:`!argparse.ArgumentParser.parses_args` did not honor
|
||||||
Patch by Ben Hsing
|
``exit_on_error=False``.
|
||||||
|
Based on patch by Ben Hsing.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue