diff --git a/Lib/argparse.py b/Lib/argparse.py index 0d8968267..2fb1da59f 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1,4 +1,5 @@ # Author: Steven J. Bethard . +# New maintainer as of 29 August 2019: Raymond Hettinger """Command-line parsing library @@ -66,6 +67,7 @@ __all__ = [ 'ArgumentParser', 'ArgumentError', 'ArgumentTypeError', + 'BooleanOptionalAction', 'FileType', 'HelpFormatter', 'ArgumentDefaultsHelpFormatter', @@ -127,7 +129,7 @@ class _AttributeHolder(object): return '%s(%s)' % (type_name, ', '.join(arg_strings)) def _get_kwargs(self): - return sorted(self.__dict__.items()) + return list(self.__dict__.items()) def _get_args(self): return [] @@ -164,15 +166,12 @@ class HelpFormatter(object): # default setting for width if width is None: - try: - width = int(_os.environ['COLUMNS']) - except (KeyError, ValueError): - width = 80 + import shutil + width = shutil.get_terminal_size().columns width -= 2 self._prog = prog self._indent_increment = indent_increment - self._max_help_position = max_help_position self._max_help_position = min(max_help_position, max(width - 20, indent_increment * 2)) self._width = width @@ -265,7 +264,7 @@ class HelpFormatter(object): invocations.append(get_invocation(subaction)) # update the maximum item length - invocation_length = max([len(s) for s in invocations]) + invocation_length = max(map(len, invocations)) action_length = invocation_length + self._current_indent self._action_max_length = max(self._action_max_length, action_length) @@ -407,13 +406,19 @@ class HelpFormatter(object): inserts[start] += ' [' else: inserts[start] = '[' - inserts[end] = ']' + if end in inserts: + inserts[end] += ']' + else: + inserts[end] = ']' else: if start in inserts: inserts[start] += ' (' else: inserts[start] = '(' - inserts[end] = ')' + if end in inserts: + inserts[end] += ')' + else: + inserts[end] = ')' for i in range(start + 1, end): inserts[i] = '|' @@ -450,7 +455,7 @@ class HelpFormatter(object): # if the Optional doesn't take a value, format is: # -s or --long if action.nargs == 0: - part = '%s' % option_string + part = action.format_usage() # if the Optional takes a value, format is: # -s ARGS or --long ARGS @@ -586,7 +591,11 @@ class HelpFormatter(object): elif action.nargs == OPTIONAL: result = '[%s]' % get_metavar(1) elif action.nargs == ZERO_OR_MORE: - result = '[%s [%s ...]]' % get_metavar(2) + metavar = get_metavar(1) + if len(metavar) == 2: + result = '[%s [%s ...]]' % metavar + else: + result = '[%s ...]' % metavar elif action.nargs == ONE_OR_MORE: result = '%s [%s ...]' % get_metavar(2) elif action.nargs == REMAINDER: @@ -596,7 +605,10 @@ class HelpFormatter(object): elif action.nargs == SUPPRESS: result = '' else: - formats = ['%s' for _ in range(action.nargs)] + try: + formats = ['%s' for _ in range(action.nargs)] + except TypeError: + raise ValueError("invalid nargs value") from None result = ' '.join(formats) % get_metavar(action.nargs) return result @@ -835,9 +847,52 @@ class Action(_AttributeHolder): ] return [(name, getattr(self, name)) for name in names] + def format_usage(self): + return self.option_strings[0] + def __call__(self, parser, namespace, values, option_string=None): raise NotImplementedError(_('.__call__() not defined')) +class BooleanOptionalAction(Action): + def __init__(self, + option_strings, + dest, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + + _option_strings = [] + for option_string in option_strings: + _option_strings.append(option_string) + + if option_string.startswith('--'): + option_string = '--no-' + option_string[2:] + _option_strings.append(option_string) + + if help is not None and default is not None: + help += f" (default: {default})" + + super().__init__( + option_strings=_option_strings, + dest=dest, + nargs=0, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + if option_string in self.option_strings: + setattr(namespace, self.dest, not option_string.startswith('--no-')) + + def format_usage(self): + return ' | '.join(self.option_strings) + class _StoreAction(Action): @@ -853,7 +908,7 @@ class _StoreAction(Action): help=None, metavar=None): if nargs == 0: - raise ValueError('nargs for store actions must be > 0; if you ' + raise ValueError('nargs for store actions must be != 0; if you ' 'have nothing to store, actions such as store ' 'true or store const may be more appropriate') if const is not None and nargs != OPTIONAL: @@ -945,7 +1000,7 @@ class _AppendAction(Action): help=None, metavar=None): if nargs == 0: - raise ValueError('nargs for append actions must be > 0; if arg ' + raise ValueError('nargs for append actions must be != 0; if arg ' 'strings are not supplying the value to append, ' 'the append const action may be more appropriate') if const is not None and nargs != OPTIONAL: @@ -1157,6 +1212,12 @@ class _SubParsersAction(Action): vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) +class _ExtendAction(_AppendAction): + def __call__(self, parser, namespace, values, option_string=None): + items = getattr(namespace, self.dest, None) + items = _copy_items(items) + items.extend(values) + setattr(namespace, self.dest, items) # ============== # Type classes @@ -1201,8 +1262,9 @@ class FileType(object): return open(string, self._mode, self._bufsize, self._encoding, self._errors) except OSError as e: - message = _("can't open '%s': %s") - raise ArgumentTypeError(message % (string, e)) + args = {'filename': string, 'error': e} + message = _("can't open '%(filename)s': %(error)s") + raise ArgumentTypeError(message % args) def __repr__(self): args = self._mode, self._bufsize @@ -1265,6 +1327,7 @@ class _ActionsContainer(object): self.register('action', 'help', _HelpAction) self.register('action', 'version', _VersionAction) self.register('action', 'parsers', _SubParsersAction) + self.register('action', 'extend', _ExtendAction) # raise an exception if the conflict handler is invalid self._get_handler() @@ -1357,6 +1420,10 @@ class _ActionsContainer(object): if not callable(type_func): raise ValueError('%r is not callable' % (type_func,)) + if type_func is FileType: + raise ValueError('%r is a FileType class object, instance of it' + ' must be passed' % (type_func,)) + # raise an error if the metavar does not match the type if hasattr(self, "_get_formatter"): try: @@ -1471,10 +1538,8 @@ class _ActionsContainer(object): # strings starting with two prefix characters are long options option_strings.append(option_string) - if option_string[0] in self.prefix_chars: - if len(option_string) > 1: - if option_string[1] in self.prefix_chars: - long_option_strings.append(option_string) + if len(option_string) > 1 and option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' dest = kwargs.pop('dest', None) @@ -1614,6 +1679,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, @@ -1628,19 +1695,14 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): argument_default=None, conflict_handler='error', add_help=True, - allow_abbrev=True): - _ActionsContainer.__init__(self, - description=description, - prefix_chars=prefix_chars, - argument_default=argument_default, - conflict_handler=conflict_handler) - # FIXME: get multiple inheritance method resolution right so we can use - # what's below instead of the modified version above - # superinit = super(ArgumentParser, self).__init__ - # superinit(description=description, - # prefix_chars=prefix_chars, - # argument_default=argument_default, - # conflict_handler=conflict_handler) + allow_abbrev=True, + exit_on_error=True): + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) # default setting for prog if prog is None: @@ -1653,6 +1715,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')) @@ -1783,15 +1846,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 @@ -2080,10 +2147,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): OPTIONAL: _('expected at most one argument'), ONE_OR_MORE: _('expected at least one argument'), } - default = ngettext('expected %s argument', + msg = nargs_errors.get(action.nargs) + if msg is None: + msg = ngettext('expected %s argument', 'expected %s arguments', action.nargs) % action.nargs - msg = nargs_errors.get(action.nargs, default) raise ArgumentError(action, msg) # return the number of arguments matched @@ -2130,24 +2198,23 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): action = self._option_string_actions[option_string] return action, option_string, explicit_arg - if self.allow_abbrev: - # search through all possible prefixes of the option string - # and all actions in the parser for possible interpretations - option_tuples = self._get_option_tuples(arg_string) + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) - # if multiple actions match, the option string was ambiguous - if len(option_tuples) > 1: - options = ', '.join([option_string - for action, option_string, explicit_arg in option_tuples]) - args = {'option': arg_string, 'matches': options} - msg = _('ambiguous option: %(option)s could match %(matches)s') - self.error(msg % args) + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + args = {'option': arg_string, 'matches': options} + msg = _('ambiguous option: %(option)s could match %(matches)s') + self.error(msg % args) - # if exactly one action matched, this segmentation is good, - # so return the parsed action - elif len(option_tuples) == 1: - option_tuple, = option_tuples - return option_tuple + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple # if it was not found as an option, but it looks like a negative # number, it was meant to be positional @@ -2171,16 +2238,17 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): # split at the '=' chars = self.prefix_chars if option_string[0] in chars and option_string[1] in chars: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - explicit_arg = None - for option_string in self._option_string_actions: - if option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) + if self.allow_abbrev: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) # single character options can be concatenated with their arguments # but multiple character options always have to have their argument diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index e849c7ba4..22cae626c 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1,6 +1,5 @@ # Author: Steven J. Bethard . -import codecs import inspect import os import shutil @@ -106,7 +105,8 @@ def stderr_to_parser_error(parse_args, *args, **kwargs): code = sys.exc_info()[1].code stdout = sys.stdout.getvalue() stderr = sys.stderr.getvalue() - raise ArgumentParserError("SystemExit", stdout, stderr, code) + raise ArgumentParserError( + "SystemExit", stdout, stderr, code) from None finally: sys.stdout = old_stdout sys.stderr = old_stderr @@ -687,6 +687,38 @@ class TestOptionalsActionStoreTrue(ParserTestCase): ('--apple', NS(apple=True)), ] +class TestBooleanOptionalAction(ParserTestCase): + """Tests BooleanOptionalAction""" + + argument_signatures = [Sig('--foo', action=argparse.BooleanOptionalAction)] + failures = ['--foo bar', '--foo=bar'] + successes = [ + ('', NS(foo=None)), + ('--foo', NS(foo=True)), + ('--no-foo', NS(foo=False)), + ('--foo --no-foo', NS(foo=False)), # useful for aliases + ('--no-foo --foo', NS(foo=True)), + ] + + def test_const(self): + # See bpo-40862 + parser = argparse.ArgumentParser() + with self.assertRaises(TypeError) as cm: + parser.add_argument('--foo', const=True, action=argparse.BooleanOptionalAction) + + self.assertIn("got an unexpected keyword argument 'const'", str(cm.exception)) + +class TestBooleanOptionalActionRequired(ParserTestCase): + """Tests BooleanOptionalAction required""" + + argument_signatures = [ + Sig('--foo', required=True, action=argparse.BooleanOptionalAction) + ] + failures = [''] + successes = [ + ('--foo', NS(foo=True)), + ('--no-foo', NS(foo=False)), + ] class TestOptionalsActionAppend(ParserTestCase): """Tests the append action for an Optional""" @@ -786,6 +818,62 @@ class TestOptionalsDisallowLongAbbreviation(ParserTestCase): ('--foonly 7 --foodle --foo 2', NS(foo='2', foodle=True, foonly='7')), ] + +class TestOptionalsDisallowLongAbbreviationPrefixChars(ParserTestCase): + """Disallowing abbreviations works with alternative prefix characters""" + + parser_signature = Sig(prefix_chars='+', allow_abbrev=False) + argument_signatures = [ + Sig('++foo'), + Sig('++foodle', action='store_true'), + Sig('++foonly'), + ] + failures = ['+foon 3', '++foon 3', '++food', '++food ++foo 2'] + successes = [ + ('', NS(foo=None, foodle=False, foonly=None)), + ('++foo 3', NS(foo='3', foodle=False, foonly=None)), + ('++foonly 7 ++foodle ++foo 2', NS(foo='2', foodle=True, foonly='7')), + ] + + +class TestDisallowLongAbbreviationAllowsShortGrouping(ParserTestCase): + """Do not allow abbreviations of long options at all""" + + parser_signature = Sig(allow_abbrev=False) + argument_signatures = [ + Sig('-r'), + Sig('-c', action='count'), + ] + failures = ['-r', '-c -r'] + successes = [ + ('', NS(r=None, c=None)), + ('-ra', NS(r='a', c=None)), + ('-rcc', NS(r='cc', c=None)), + ('-cc', NS(r=None, c=2)), + ('-cc -ra', NS(r='a', c=2)), + ('-ccrcc', NS(r='cc', c=2)), + ] + + +class TestDisallowLongAbbreviationAllowsShortGroupingPrefix(ParserTestCase): + """Short option grouping works with custom prefix and allow_abbrev=False""" + + parser_signature = Sig(prefix_chars='+', allow_abbrev=False) + argument_signatures = [ + Sig('+r'), + Sig('+c', action='count'), + ] + failures = ['+r', '+c +r'] + successes = [ + ('', NS(r=None, c=None)), + ('+ra', NS(r='a', c=None)), + ('+rcc', NS(r='cc', c=None)), + ('+cc', NS(r=None, c=2)), + ('+cc +ra', NS(r='a', c=2)), + ('+ccrcc', NS(r='cc', c=2)), + ] + + # ================ # Positional tests # ================ @@ -1619,6 +1707,24 @@ class TestFileTypeOpenArgs(TestCase): m.assert_called_with('foo', *args) +class TestFileTypeMissingInitialization(TestCase): + """ + Test that add_argument throws an error if FileType class + object was passed instead of instance of FileType + """ + + def test(self): + parser = argparse.ArgumentParser() + with self.assertRaises(ValueError) as cm: + parser.add_argument('-x', type=argparse.FileType) + + self.assertEqual( + '%r is a FileType class object, instance of it must be passed' + % (argparse.FileType,), + str(cm.exception) + ) + + class TestTypeCallable(ParserTestCase): """Test some callables as option/argument types""" @@ -1786,6 +1892,15 @@ class TestActionRegistration(TestCase): self.assertEqual(parser.parse_args(['42']), NS(badger='foo[42]')) +class TestActionExtend(ParserTestCase): + argument_signatures = [ + Sig('--foo', action="extend", nargs="+", type=str), + ] + failures = () + successes = [ + ('--foo f1 --foo f2 f3 f4', NS(foo=['f1', 'f2', 'f3', 'f4'])), + ] + # ================ # Subparsers tests # ================ @@ -2094,7 +2209,7 @@ class TestAddSubparsers(TestCase): def test_subparser2_help(self): self._test_subparser_help('5.0 2 -h', textwrap.dedent('''\ - usage: PROG bar 2 [-h] [-y {1,2,3}] [z [z ...]] + usage: PROG bar 2 [-h] [-y {1,2,3}] [z ...] 2 description @@ -2628,10 +2743,10 @@ class TestMutuallyExclusiveOptionalAndPositional(MEMixin, TestCase): ] usage_when_not_required = '''\ - usage: PROG [-h] [--foo | --spam SPAM | badger [badger ...]] + usage: PROG [-h] [--foo | --spam SPAM | badger ...] ''' usage_when_required = '''\ - usage: PROG [-h] (--foo | --spam SPAM | badger [badger ...]) + usage: PROG [-h] (--foo | --spam SPAM | badger ...) ''' help = '''\ @@ -2768,6 +2883,46 @@ class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase): -c c help ''' +class TestMutuallyExclusiveNested(MEMixin, TestCase): + + def get_parser(self, required): + parser = ErrorRaisingArgumentParser(prog='PROG') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('-a') + group.add_argument('-b') + group2 = group.add_mutually_exclusive_group(required=required) + group2.add_argument('-c') + group2.add_argument('-d') + group3 = group2.add_mutually_exclusive_group(required=required) + group3.add_argument('-e') + group3.add_argument('-f') + return parser + + usage_when_not_required = '''\ + usage: PROG [-h] [-a A | -b B | [-c C | -d D | [-e E | -f F]]] + ''' + usage_when_required = '''\ + usage: PROG [-h] (-a A | -b B | (-c C | -d D | (-e E | -f F))) + ''' + + help = '''\ + + optional arguments: + -h, --help show this help message and exit + -a A + -b B + -c C + -d D + -e E + -f F + ''' + + # We are only interested in testing the behavior of format_usage(). + test_failures_when_not_required = None + test_failures_when_required = None + test_successes_when_not_required = None + test_successes_when_required = None + # ================================================= # Mutually exclusive group in parent parser tests # ================================================= @@ -3371,6 +3526,10 @@ class TestHelpUsage(HelpTestCase): Sig('a', help='a'), Sig('b', help='b', nargs=2), Sig('c', help='c', nargs='?'), + Sig('--foo', help='Whether to foo', action=argparse.BooleanOptionalAction), + Sig('--bar', help='Whether to bar', default=True, + action=argparse.BooleanOptionalAction), + Sig('-f', '--foobar', '--barfoo', action=argparse.BooleanOptionalAction), ] argument_group_signatures = [ (Sig('group'), [ @@ -3381,26 +3540,32 @@ class TestHelpUsage(HelpTestCase): ]) ] usage = '''\ - usage: PROG [-h] [-w W [W ...]] [-x [X [X ...]]] [-y [Y]] [-z Z Z Z] - a b b [c] [d [d ...]] e [e ...] + usage: PROG [-h] [-w W [W ...]] [-x [X ...]] [--foo | --no-foo] + [--bar | --no-bar] + [-f | --foobar | --no-foobar | --barfoo | --no-barfoo] [-y [Y]] + [-z Z Z Z] + a b b [c] [d ...] e [e ...] ''' help = usage + '''\ positional arguments: - a a - b b - c c + a a + b b + c c optional arguments: - -h, --help show this help message and exit - -w W [W ...] w - -x [X [X ...]] x + -h, --help show this help message and exit + -w W [W ...] w + -x [X ...] x + --foo, --no-foo Whether to foo + --bar, --no-bar Whether to bar (default: True) + -f, --foobar, --no-foobar, --barfoo, --no-barfoo group: - -y [Y] y - -z Z Z Z z - d d - e e + -y [Y] y + -z Z Z Z z + d d + e e ''' version = '' @@ -4218,7 +4383,6 @@ class TestHelpSubparsersWithHelpOrdering(HelpTestCase): class TestHelpMetavarTypeFormatter(HelpTestCase): - """""" def custom_type(string): return string @@ -4569,7 +4733,7 @@ class TestStrings(TestCase): def test_namespace(self): ns = argparse.Namespace(foo=42, bar='spam') - string = "Namespace(bar='spam', foo=42)" + string = "Namespace(foo=42, bar='spam')" self.assertStringEqual(ns, string) def test_namespace_starkwargs_notidentifier(self): @@ -4995,7 +5159,7 @@ class TestAddArgumentMetavar(TestCase): self.do_test_exception(nargs="*", metavar=tuple()) def test_nargs_zeroormore_metavar_length1(self): - self.do_test_exception(nargs="*", metavar=("1",)) + self.do_test_no_exception(nargs="*", metavar=("1",)) def test_nargs_zeroormore_metavar_length2(self): self.do_test_no_exception(nargs="*", metavar=("1", "2")) @@ -5105,6 +5269,35 @@ class TestAddArgumentMetavar(TestCase): def test_nargs_3_metavar_length3(self): self.do_test_no_exception(nargs=3, metavar=("1", "2", "3")) + +class TestInvalidNargs(TestCase): + + EXPECTED_INVALID_MESSAGE = "invalid nargs value" + EXPECTED_RANGE_MESSAGE = ("nargs for store actions must be != 0; if you " + "have nothing to store, actions such as store " + "true or store const may be more appropriate") + + def do_test_range_exception(self, nargs): + parser = argparse.ArgumentParser() + with self.assertRaises(ValueError) as cm: + parser.add_argument("--foo", nargs=nargs) + self.assertEqual(cm.exception.args[0], self.EXPECTED_RANGE_MESSAGE) + + def do_test_invalid_exception(self, nargs): + parser = argparse.ArgumentParser() + with self.assertRaises(ValueError) as cm: + parser.add_argument("--foo", nargs=nargs) + self.assertEqual(cm.exception.args[0], self.EXPECTED_INVALID_MESSAGE) + + # Unit tests for different values of nargs + + def test_nargs_alphabetic(self): + self.do_test_invalid_exception(nargs='a') + self.do_test_invalid_exception(nargs="abcd") + + def test_nargs_zero(self): + self.do_test_range_exception(nargs=0) + # ============================ # from argparse import * tests # ============================ @@ -5149,6 +5342,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/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index c2a7417a2..0538e7449 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -149,7 +149,7 @@ impl PyFunction { kwargs.set_item(name, value, vm)?; } else { return Err( - vm.new_type_error(format!("Got an unexpected keyword argument '{}'", name)) + vm.new_type_error(format!("got an unexpected keyword argument '{}'", name)) ); } }