From f545638b5701652ffbe1774989533cdf5bc6631e Mon Sep 17 00:00:00 2001 From: Hai Shi Date: Thu, 12 Sep 2019 05:56:05 -0500 Subject: [PATCH] bpo-9938: Add optional keyword argument exit_on_error to argparse.ArgumentParser (GH-15362) Co-Authored-by: Xuanji Li https://bugs.python.org/issue9938 Automerge-Triggered-By: @matrixise --- Doc/library/argparse.rst | 30 ++++++++++++++++++- Lib/argparse.py | 26 ++++++++++------ Lib/test/test_argparse.py | 15 ++++++++++ .../2019-08-21-16-38-56.bpo-9938.t3G7N9.rst | 1 + 4 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-08-21-16-38-56.bpo-9938.t3G7N9.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 368b1cfebf0..c2cf7d320b3 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=, 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 ddfd772c112..2e46e76a499 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 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): 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 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): 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 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): 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 86ec6cca51a..464db290699 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5262,6 +5262,21 @@ class TestWrappingMetavar(TestCase): ''')) +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 00000000000..4cb89893159 --- /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`.