https://github.com/python/cpython/commit/81a654a3425eaa05a51342509089533c1f623f1b
commit: 81a654a3425eaa05a51342509089533c1f623f1b
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-06-28T17:21:59+03:00
summary:

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.

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 f1c3808869aab3..83189115bf85f7 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1791,7 +1791,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))
@@ -1846,7 +1846,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):
@@ -2135,7 +2136,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
@@ -2151,7 +2152,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
@@ -2178,7 +2179,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
@@ -2258,7 +2259,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
@@ -2318,7 +2319,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
@@ -2375,8 +2376,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 11eb081f45701e..84fe74a1063fb6 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -2147,7 +2147,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')
@@ -6042,7 +6044,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):
@@ -6053,9 +6056,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 f311077d6bfc41..eac5bab3e9fe6d 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 :func:`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]

Reply via email to