https://github.com/python/cpython/commit/8ea6cc14a5178b456278c35f63b0859c6b5f4f64
commit: 8ea6cc14a5178b456278c35f63b0859c6b5f4f64
branch: 3.12
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-06-28T14:52:07Z
summary:
[3.12] gh-121018: Fix more cases of exiting in argparse when
exit_on_error=False (GH-121056) (GH-121129)
* 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.
(cherry picked from commit 81a654a3425eaa05a51342509089533c1f623f1b)
files:
M Lib/argparse.py
M Lib/test/test_argparse.py
M Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 0890718270f46e..e4892955e4ff09 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1843,7 +1843,7 @@ def _get_kwargs(self):
# ==================================
def add_subparsers(self, **kwargs):
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
kwargs.setdefault('parser_class', type(self))
@@ -1898,7 +1898,8 @@ def parse_args(self, args=None, namespace=None):
msg = _('unrecognized arguments: %s') % ' '.join(argv)
if self.exit_on_error:
self.error(msg)
- raise ArgumentError(None, msg)
+ else:
+ raise ArgumentError(None, msg)
return args
def parse_known_args(self, args=None, namespace=None):
@@ -2177,7 +2178,7 @@ def consume_positionals(start_index):
self._get_value(action, action.default))
if required_actions:
- self.error(_('the following arguments are required: %s') %
+ raise ArgumentError(None, _('the following arguments are required:
%s') %
', '.join(required_actions))
# make sure all required groups had one option present
@@ -2193,7 +2194,7 @@ def consume_positionals(start_index):
for action in group._group_actions
if action.help is not SUPPRESS]
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 namespace, extras
@@ -2220,7 +2221,7 @@ def _read_args_from_files(self, arg_strings):
arg_strings = self._read_args_from_files(arg_strings)
new_arg_strings.extend(arg_strings)
except OSError as err:
- self.error(str(err))
+ raise ArgumentError(None, str(err))
# return the modified argument list
return new_arg_strings
@@ -2300,7 +2301,7 @@ def _parse_optional(self, arg_string):
for action, option_string, sep, explicit_arg in option_tuples])
args = {'option': arg_string, 'matches': options}
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,
# so return the parsed action
@@ -2360,7 +2361,7 @@ def _get_option_tuples(self, option_string):
# shouldn't ever get here
else:
- self.error(_('unexpected option string: %s') % option_string)
+ raise ArgumentError(None, _('unexpected option string: %s') %
option_string)
# return the collected option tuples
return result
@@ -2417,8 +2418,11 @@ def _get_nargs_pattern(self, action):
def parse_intermixed_args(self, args=None, namespace=None):
args, argv = self.parse_known_intermixed_args(args, namespace)
if argv:
- msg = _('unrecognized arguments: %s')
- self.error(msg % ' '.join(argv))
+ msg = _('unrecognized arguments: %s') % ' '.join(argv)
+ if self.exit_on_error:
+ self.error(msg)
+ else:
+ raise ArgumentError(None, msg)
return args
def parse_known_intermixed_args(self, args=None, namespace=None):
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index a4d6c4a35256f9..6a7723e8cd1e26 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -2126,7 +2126,9 @@ def _get_parser(self, subparser_help=False,
prefix_chars=None,
else:
subparsers_kwargs['help'] = 'command help'
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
parser1_kwargs = dict(description='1 description')
@@ -5732,7 +5734,8 @@ def test_help_with_metavar(self):
class TestExitOnError(TestCase):
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)
def test_exit_on_error_with_good_args(self):
@@ -5743,9 +5746,44 @@ def test_exit_on_error_with_bad_args(self):
with self.assertRaises(argparse.ArgumentError):
self.parser.parse_args('--integers a'.split())
- def test_exit_on_error_with_unrecognized_args(self):
- with self.assertRaises(argparse.ArgumentError):
- self.parser.parse_args('--foo bar'.split())
+ def test_unrecognized_args(self):
+ self.assertRaisesRegex(argparse.ArgumentError,
+ '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():
# Remove global references to avoid looking like we have refleaks.
diff --git
a/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst
b/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst
index fdc3c5f9e459af..b1d219dea3d36a 100644
--- a/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst
+++ b/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst
@@ -1,2 +1,3 @@
-Fixed an issue where :meth:`!argparse.ArgumentParser.parses_args` did not
honor ``exit_on_error=False`` when given unrecognized arguments.
-Patch by Ben Hsing.
+Fixed issues where :meth:`!argparse.ArgumentParser.parses_args` did not honor
+``exit_on_error=False``.
+Based on patch by Ben Hsing.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]