[Python-checkins] bpo-9938: Add optional keyword argument exit_on_error to argparse.ArgumentParser (GH-15362)

Miss Islington (bot) webhook-mailer at python.org
Thu Sep 12 06:56:09 EDT 2019


https://github.com/python/cpython/commit/f545638b5701652ffbe1774989533cdf5bc6631e
commit: f545638b5701652ffbe1774989533cdf5bc6631e
branch: master
author: Hai Shi <shihai1992 at gmail.com>
committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
date: 2019-09-12T03:56:05-07:00
summary:

bpo-9938: Add optional keyword argument exit_on_error to argparse.ArgumentParser (GH-15362)



Co-Authored-by: Xuanji Li <xuanji at gmail.com>


https://bugs.python.org/issue9938



Automerge-Triggered-By: @matrixise

files:
A Misc/NEWS.d/next/Library/2019-08-21-16-38-56.bpo-9938.t3G7N9.rst
M Doc/library/argparse.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index 368b1cfebf05..c2cf7d320b3f 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -142,7 +142,7 @@ ArgumentParser objects
                           formatter_class=argparse.HelpFormatter, \
                           prefix_chars='-', fromfile_prefix_chars=None, \
                           argument_default=None, conflict_handler='error', \
-                          add_help=True, allow_abbrev=True)
+                          add_help=True, allow_abbrev=True, exit_on_error=True)
 
    Create a new :class:`ArgumentParser` object. All parameters should be passed
    as keyword arguments. Each parameter has its own more detailed description
@@ -179,6 +179,9 @@ ArgumentParser objects
    * allow_abbrev_ - Allows long options to be abbreviated if the
      abbreviation is unambiguous. (default: ``True``)
 
+   * exit_on_error_ - Determines whether or not ArgumentParser exits with
+     error info when an error occurs. (default: ``True``)
+
    .. versionchanged:: 3.5
       *allow_abbrev* parameter was added.
 
@@ -186,6 +189,9 @@ ArgumentParser objects
       In previous versions, *allow_abbrev* also disabled grouping of short
       flags such as ``-vv`` to mean ``-v -v``.
 
+   .. versionchanged:: 3.9
+      *exit_on_error* parameter was added.
+
 The following sections describe how each of these are used.
 
 
@@ -647,6 +653,28 @@ the help options::
      +h, ++help  show this help message and exit
 
 
+exit_on_error
+^^^^^^^^^^^^^
+
+Normally, when you pass an invalid argument list to the :meth:`~ArgumentParser.parse_args`
+method of an :class:`ArgumentParser`, it will exit with error info.
+
+If the user would like catch errors manually, the feature can be enable by setting
+``exit_on_error`` to ``False``::
+
+   >>> parser = argparse.ArgumentParser(exit_on_error=False)
+   >>> parser.add_argument('--integers', type=int)
+   _StoreAction(option_strings=['--integers'], dest='integers', nargs=None, const=None, default=None, type=<class 'int'>, choices=None, help=None, metavar=None)
+   >>> try:
+   ...     parser.parse_args('--integers a'.split())
+   ... except argparse.ArgumentError:
+   ...     print('Catching an argumentError')
+   ...
+   Catching an argumentError
+
+.. versionadded:: 3.9
+
+
 The add_argument() method
 -------------------------
 
diff --git a/Lib/argparse.py b/Lib/argparse.py
index ddfd772c1126..2e46e76a4997 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1630,6 +1630,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
         - conflict_handler -- String indicating how to handle conflicts
         - add_help -- Add a -h/-help option
         - allow_abbrev -- Allow long options to be abbreviated unambiguously
+        - exit_on_error -- Determines whether or not ArgumentParser exits with
+            error info when an error occurs
     """
 
     def __init__(self,
@@ -1644,7 +1646,8 @@ def __init__(self,
                  argument_default=None,
                  conflict_handler='error',
                  add_help=True,
-                 allow_abbrev=True):
+                 allow_abbrev=True,
+                 exit_on_error=True):
 
         superinit = super(ArgumentParser, self).__init__
         superinit(description=description,
@@ -1663,6 +1666,7 @@ def __init__(self,
         self.fromfile_prefix_chars = fromfile_prefix_chars
         self.add_help = add_help
         self.allow_abbrev = allow_abbrev
+        self.exit_on_error = exit_on_error
 
         add_group = self.add_argument_group
         self._positionals = add_group(_('positional arguments'))
@@ -1793,15 +1797,19 @@ def parse_known_args(self, args=None, namespace=None):
                 setattr(namespace, dest, self._defaults[dest])
 
         # parse the arguments and exit if there are any errors
-        try:
+        if self.exit_on_error:
+            try:
+                namespace, args = self._parse_known_args(args, namespace)
+            except ArgumentError:
+                err = _sys.exc_info()[1]
+                self.error(str(err))
+        else:
             namespace, args = self._parse_known_args(args, namespace)
-            if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
-                args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
-                delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
-            return namespace, args
-        except ArgumentError:
-            err = _sys.exc_info()[1]
-            self.error(str(err))
+
+        if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+            args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
+            delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
+        return namespace, args
 
     def _parse_known_args(self, arg_strings, namespace):
         # replace arg strings that are file references
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 86ec6cca51ac..464db2906998 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -5262,6 +5262,21 @@ def test_help_with_metavar(self):
             '''))
 
 
+class TestExitOnError(TestCase):
+
+    def setUp(self):
+        self.parser = argparse.ArgumentParser(exit_on_error=False)
+        self.parser.add_argument('--integers', metavar='N', type=int)
+
+    def test_exit_on_error_with_good_args(self):
+        ns = self.parser.parse_args('--integers 4'.split())
+        self.assertEqual(ns, argparse.Namespace(integers=4))
+
+    def test_exit_on_error_with_bad_args(self):
+        with self.assertRaises(argparse.ArgumentError):
+            self.parser.parse_args('--integers a'.split())
+
+
 def test_main():
     support.run_unittest(__name__)
     # Remove global references to avoid looking like we have refleaks.
diff --git a/Misc/NEWS.d/next/Library/2019-08-21-16-38-56.bpo-9938.t3G7N9.rst b/Misc/NEWS.d/next/Library/2019-08-21-16-38-56.bpo-9938.t3G7N9.rst
new file mode 100644
index 000000000000..4cb89893159e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-08-21-16-38-56.bpo-9938.t3G7N9.rst
@@ -0,0 +1 @@
+Add optional keyword argument ``exit_on_error`` for :class:`ArgumentParser`.



More information about the Python-checkins mailing list