mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Merge pull request #4592 from carlosmiei/argparse
feat(argparse): update to 3.11
This commit is contained in:
2 files changed
+123
-45
No files matched your search
Vendored
+53
-15
@@ -89,6 +89,8 @@ import os as _os
|
||||
import re as _re
|
||||
import sys as _sys
|
||||
|
||||
import warnings
|
||||
|
||||
from gettext import gettext as _, ngettext
|
||||
|
||||
SUPPRESS = '==SUPPRESS=='
|
||||
@@ -151,6 +153,7 @@ def _copy_items(items):
|
||||
# Formatting Help
|
||||
# ===============
|
||||
|
||||
|
||||
class HelpFormatter(object):
|
||||
"""Formatter for generating usage messages and argument help strings.
|
||||
|
||||
@@ -693,8 +696,19 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
|
||||
"""
|
||||
|
||||
def _get_help_string(self, action):
|
||||
"""
|
||||
Add the default value to the option help message.
|
||||
|
||||
ArgumentDefaultsHelpFormatter and BooleanOptionalAction when it isn't
|
||||
already present. This code will do that, detecting cornercases to
|
||||
prevent duplicates or cases where it wouldn't make sense to the end
|
||||
user.
|
||||
"""
|
||||
help = action.help
|
||||
if '%(default)' not in action.help:
|
||||
if help is None:
|
||||
help = ''
|
||||
|
||||
if '%(default)' not in help:
|
||||
if action.default is not SUPPRESS:
|
||||
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
|
||||
if action.option_strings or action.nargs in defaulting_nargs:
|
||||
@@ -702,6 +716,7 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
|
||||
return help
|
||||
|
||||
|
||||
|
||||
class MetavarTypeHelpFormatter(HelpFormatter):
|
||||
"""Help message formatter which uses the argument 'type' as the default
|
||||
metavar value (instead of the argument 'dest')
|
||||
@@ -717,7 +732,6 @@ class MetavarTypeHelpFormatter(HelpFormatter):
|
||||
return action.type.__name__
|
||||
|
||||
|
||||
|
||||
# =====================
|
||||
# Options and Arguments
|
||||
# =====================
|
||||
@@ -752,7 +766,7 @@ class ArgumentError(Exception):
|
||||
if self.argument_name is None:
|
||||
format = '%(message)s'
|
||||
else:
|
||||
format = 'argument %(argument_name)s: %(message)s'
|
||||
format = _('argument %(argument_name)s: %(message)s')
|
||||
return format % dict(message=self.message,
|
||||
argument_name=self.argument_name)
|
||||
|
||||
@@ -860,6 +874,7 @@ class Action(_AttributeHolder):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
raise NotImplementedError(_('.__call__() not defined'))
|
||||
|
||||
|
||||
class BooleanOptionalAction(Action):
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
@@ -879,9 +894,6 @@ class BooleanOptionalAction(Action):
|
||||
option_string = '--no-' + option_string[2:]
|
||||
_option_strings.append(option_string)
|
||||
|
||||
if help is not None and default is not None and default is not SUPPRESS:
|
||||
help += " (default: %(default)s)"
|
||||
|
||||
super().__init__(
|
||||
option_strings=_option_strings,
|
||||
dest=dest,
|
||||
@@ -893,6 +905,7 @@ class BooleanOptionalAction(Action):
|
||||
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-'))
|
||||
@@ -941,7 +954,7 @@ class _StoreConstAction(Action):
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest,
|
||||
const,
|
||||
const=None,
|
||||
default=None,
|
||||
required=False,
|
||||
help=None,
|
||||
@@ -1036,7 +1049,7 @@ class _AppendConstAction(Action):
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest,
|
||||
const,
|
||||
const=None,
|
||||
default=None,
|
||||
required=False,
|
||||
help=None,
|
||||
@@ -1168,6 +1181,13 @@ class _SubParsersAction(Action):
|
||||
|
||||
aliases = kwargs.pop('aliases', ())
|
||||
|
||||
if name in self._name_parser_map:
|
||||
raise ArgumentError(self, _('conflicting subparser: %s') % name)
|
||||
for alias in aliases:
|
||||
if alias in self._name_parser_map:
|
||||
raise ArgumentError(
|
||||
self, _('conflicting subparser alias: %s') % alias)
|
||||
|
||||
# create a pseudo-action to hold the choice help
|
||||
if 'help' in kwargs:
|
||||
help = kwargs.pop('help')
|
||||
@@ -1648,6 +1668,14 @@ class _ArgumentGroup(_ActionsContainer):
|
||||
super(_ArgumentGroup, self)._remove_action(action)
|
||||
self._group_actions.remove(action)
|
||||
|
||||
def add_argument_group(self, *args, **kwargs):
|
||||
warnings.warn(
|
||||
"Nesting argument groups is deprecated.",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return super().add_argument_group(*args, **kwargs)
|
||||
|
||||
|
||||
class _MutuallyExclusiveGroup(_ArgumentGroup):
|
||||
|
||||
@@ -1668,6 +1696,14 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
|
||||
self._container._remove_action(action)
|
||||
self._group_actions.remove(action)
|
||||
|
||||
def add_mutually_exclusive_group(self, *args, **kwargs):
|
||||
warnings.warn(
|
||||
"Nesting mutually exclusive groups is deprecated.",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return super().add_mutually_exclusive_group(*args, **kwargs)
|
||||
|
||||
|
||||
class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
"""Object for parsing command line strings into Python objects.
|
||||
@@ -1857,8 +1893,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
if self.exit_on_error:
|
||||
try:
|
||||
namespace, args = self._parse_known_args(args, namespace)
|
||||
except ArgumentError:
|
||||
err = _sys.exc_info()[1]
|
||||
except ArgumentError as err:
|
||||
self.error(str(err))
|
||||
else:
|
||||
namespace, args = self._parse_known_args(args, namespace)
|
||||
@@ -1962,7 +1997,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
# arguments, try to parse more single-dash options out
|
||||
# of the tail of the option string
|
||||
chars = self.prefix_chars
|
||||
if arg_count == 0 and option_string[1] not in chars:
|
||||
if (
|
||||
arg_count == 0
|
||||
and option_string[1] not in chars
|
||||
and explicit_arg != ''
|
||||
):
|
||||
action_tuples.append((action, [], option_string))
|
||||
char = option_string[0]
|
||||
option_string = char + explicit_arg[0]
|
||||
@@ -2133,8 +2172,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
arg_strings.append(arg)
|
||||
arg_strings = self._read_args_from_files(arg_strings)
|
||||
new_arg_strings.extend(arg_strings)
|
||||
except OSError:
|
||||
err = _sys.exc_info()[1]
|
||||
except OSError as err:
|
||||
self.error(str(err))
|
||||
|
||||
# return the modified argument list
|
||||
@@ -2484,9 +2522,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
result = type_func(arg_string)
|
||||
|
||||
# ArgumentTypeErrors indicate errors
|
||||
except ArgumentTypeError:
|
||||
except ArgumentTypeError as err:
|
||||
name = getattr(action.type, '__name__', repr(action.type))
|
||||
msg = str(_sys.exc_info()[1])
|
||||
msg = str(err)
|
||||
raise ArgumentError(action, msg)
|
||||
|
||||
# TypeErrors or ValueErrors also indicate errors
|
||||
|
||||
Vendored
+70
-30
@@ -11,6 +11,7 @@ import textwrap
|
||||
import tempfile
|
||||
import unittest
|
||||
import argparse
|
||||
import warnings
|
||||
|
||||
from test.support import os_helper
|
||||
from unittest import mock
|
||||
@@ -40,11 +41,11 @@ class TestCase(unittest.TestCase):
|
||||
# The tests assume that line wrapping occurs at 80 columns, but this
|
||||
# behaviour can be overridden by setting the COLUMNS environment
|
||||
# variable. To ensure that this width is used, set COLUMNS to 80.
|
||||
env = os_helper.EnvironmentVarGuard()
|
||||
env = self.enterContext(os_helper.EnvironmentVarGuard())
|
||||
env['COLUMNS'] = '80'
|
||||
self.addCleanup(env.__exit__)
|
||||
|
||||
|
||||
@os_helper.skip_unless_working_chmod
|
||||
class TempDirMixin(object):
|
||||
|
||||
def setUp(self):
|
||||
@@ -295,7 +296,7 @@ class TestOptionalsSingleDashCombined(ParserTestCase):
|
||||
Sig('-z'),
|
||||
]
|
||||
failures = ['a', '--foo', '-xa', '-x --foo', '-x -z', '-z -x',
|
||||
'-yx', '-yz a', '-yyyx', '-yyyza', '-xyza']
|
||||
'-yx', '-yz a', '-yyyx', '-yyyza', '-xyza', '-x=']
|
||||
successes = [
|
||||
('', NS(x=False, yyy=None, z=None)),
|
||||
('-x', NS(x=True, yyy=None, z=None)),
|
||||
@@ -769,6 +770,25 @@ class TestOptionalsActionAppendWithDefault(ParserTestCase):
|
||||
]
|
||||
|
||||
|
||||
class TestConstActionsMissingConstKwarg(ParserTestCase):
|
||||
"""Tests that const gets default value of None when not provided"""
|
||||
|
||||
argument_signatures = [
|
||||
Sig('-f', action='append_const'),
|
||||
Sig('--foo', action='append_const'),
|
||||
Sig('-b', action='store_const'),
|
||||
Sig('--bar', action='store_const')
|
||||
]
|
||||
failures = ['-f v', '--foo=bar', '--foo bar']
|
||||
successes = [
|
||||
('', NS(f=None, foo=None, b=None, bar=None)),
|
||||
('-f', NS(f=[None], foo=None, b=None, bar=None)),
|
||||
('--foo', NS(f=None, foo=[None], b=None, bar=None)),
|
||||
('-b', NS(f=None, foo=None, b=None, bar=None)),
|
||||
('--bar', NS(f=None, foo=None, b=None, bar=None)),
|
||||
]
|
||||
|
||||
|
||||
class TestOptionalsActionAppendConst(ParserTestCase):
|
||||
"""Tests the append_const action for an Optional"""
|
||||
|
||||
@@ -1703,8 +1723,7 @@ class WFile(object):
|
||||
return self.name == other.name
|
||||
|
||||
|
||||
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
|
||||
"non-root user required")
|
||||
@os_helper.skip_if_dac_override
|
||||
class TestFileTypeW(TempDirMixin, ParserTestCase):
|
||||
"""Test the FileType option/argument type for writing files"""
|
||||
|
||||
@@ -1726,8 +1745,8 @@ class TestFileTypeW(TempDirMixin, ParserTestCase):
|
||||
('-x - -', NS(x=eq_stdout, spam=eq_stdout)),
|
||||
]
|
||||
|
||||
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
|
||||
"non-root user required")
|
||||
|
||||
@os_helper.skip_if_dac_override
|
||||
class TestFileTypeX(TempDirMixin, ParserTestCase):
|
||||
"""Test the FileType option/argument type for writing new files only"""
|
||||
|
||||
@@ -1747,8 +1766,7 @@ class TestFileTypeX(TempDirMixin, ParserTestCase):
|
||||
]
|
||||
|
||||
|
||||
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
|
||||
"non-root user required")
|
||||
@os_helper.skip_if_dac_override
|
||||
class TestFileTypeWB(TempDirMixin, ParserTestCase):
|
||||
"""Test the FileType option/argument type for writing binary files"""
|
||||
|
||||
@@ -1765,8 +1783,7 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
|
||||
]
|
||||
|
||||
|
||||
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
|
||||
"non-root user required")
|
||||
@os_helper.skip_if_dac_override
|
||||
class TestFileTypeXB(TestFileTypeX):
|
||||
"Test the FileType option/argument type for writing new binary files only"
|
||||
|
||||
@@ -2245,8 +2262,7 @@ class TestAddSubparsers(TestCase):
|
||||
main description
|
||||
|
||||
positional arguments:
|
||||
foo
|
||||
|
||||
foo \n
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
'''))
|
||||
@@ -2262,8 +2278,7 @@ class TestAddSubparsers(TestCase):
|
||||
main description
|
||||
|
||||
positional arguments:
|
||||
{}
|
||||
|
||||
{} \n
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
'''))
|
||||
@@ -3041,15 +3056,24 @@ class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase):
|
||||
|
||||
class TestMutuallyExclusiveNested(MEMixin, TestCase):
|
||||
|
||||
# Nesting mutually exclusive groups is an undocumented feature
|
||||
# that came about by accident through inheritance and has been
|
||||
# the source of many bugs. It is deprecated and this test should
|
||||
# eventually be removed along with it.
|
||||
|
||||
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)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
group2 = group.add_mutually_exclusive_group(required=required)
|
||||
group2.add_argument('-c')
|
||||
group2.add_argument('-d')
|
||||
group3 = group2.add_mutually_exclusive_group(required=required)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
group3 = group2.add_mutually_exclusive_group(required=required)
|
||||
group3.add_argument('-e')
|
||||
group3.add_argument('-f')
|
||||
return parser
|
||||
@@ -3321,6 +3345,7 @@ class TestHelpFormattingMetaclass(type):
|
||||
def _test(self, tester, parser_text):
|
||||
expected_text = getattr(tester, self.func_suffix)
|
||||
expected_text = textwrap.dedent(expected_text)
|
||||
tester.maxDiff = None
|
||||
tester.assertEqual(expected_text, parser_text)
|
||||
|
||||
def test_format(self, tester):
|
||||
@@ -3400,9 +3425,8 @@ class TestShortColumns(HelpTestCase):
|
||||
but we don't want any exceptions thrown in such cases. Only ugly representation.
|
||||
'''
|
||||
def setUp(self):
|
||||
env = os_helper.EnvironmentVarGuard()
|
||||
env = self.enterContext(os_helper.EnvironmentVarGuard())
|
||||
env.set("COLUMNS", '15')
|
||||
self.addCleanup(env.__exit__)
|
||||
|
||||
parser_signature = TestHelpBiggerOptionals.parser_signature
|
||||
argument_signatures = TestHelpBiggerOptionals.argument_signatures
|
||||
@@ -3716,7 +3740,7 @@ class TestHelpUsage(HelpTestCase):
|
||||
-w W [W ...] w
|
||||
-x [X ...] x
|
||||
--foo, --no-foo Whether to foo
|
||||
--bar, --no-bar Whether to bar (default: True)
|
||||
--bar, --no-bar Whether to bar
|
||||
-f, --foobar, --no-foobar, --barfoo, --no-barfoo
|
||||
--bazz, --no-bazz Bazz!
|
||||
|
||||
@@ -4396,6 +4420,8 @@ class TestHelpArgumentDefaults(HelpTestCase):
|
||||
Sig('--bar', action='store_true', help='bar help'),
|
||||
Sig('--taz', action=argparse.BooleanOptionalAction,
|
||||
help='Whether to taz it', default=True),
|
||||
Sig('--corge', action=argparse.BooleanOptionalAction,
|
||||
help='Whether to corge it', default=argparse.SUPPRESS),
|
||||
Sig('--quux', help="Set the quux", default=42),
|
||||
Sig('spam', help='spam help'),
|
||||
Sig('badger', nargs='?', default='wooden', help='badger help'),
|
||||
@@ -4405,8 +4431,8 @@ class TestHelpArgumentDefaults(HelpTestCase):
|
||||
[Sig('--baz', type=int, default=42, help='baz help')]),
|
||||
]
|
||||
usage = '''\
|
||||
usage: PROG [-h] [--foo FOO] [--bar] [--taz | --no-taz] [--quux QUUX]
|
||||
[--baz BAZ]
|
||||
usage: PROG [-h] [--foo FOO] [--bar] [--taz | --no-taz] [--corge | --no-corge]
|
||||
[--quux QUUX] [--baz BAZ]
|
||||
spam [badger]
|
||||
'''
|
||||
help = usage + '''\
|
||||
@@ -4414,20 +4440,21 @@ class TestHelpArgumentDefaults(HelpTestCase):
|
||||
description
|
||||
|
||||
positional arguments:
|
||||
spam spam help
|
||||
badger badger help (default: wooden)
|
||||
spam spam help
|
||||
badger badger help (default: wooden)
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--foo FOO foo help - oh and by the way, None
|
||||
--bar bar help (default: False)
|
||||
--taz, --no-taz Whether to taz it (default: True)
|
||||
--quux QUUX Set the quux (default: 42)
|
||||
-h, --help show this help message and exit
|
||||
--foo FOO foo help - oh and by the way, None
|
||||
--bar bar help (default: False)
|
||||
--taz, --no-taz Whether to taz it (default: True)
|
||||
--corge, --no-corge Whether to corge it
|
||||
--quux QUUX Set the quux (default: 42)
|
||||
|
||||
title:
|
||||
description
|
||||
|
||||
--baz BAZ baz help (default: 42)
|
||||
--baz BAZ baz help (default: 42)
|
||||
'''
|
||||
version = ''
|
||||
|
||||
@@ -4777,6 +4804,19 @@ class TestConflictHandling(TestCase):
|
||||
--spam NEW_SPAM
|
||||
'''))
|
||||
|
||||
def test_subparser_conflict(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
sp = parser.add_subparsers()
|
||||
sp.add_parser('fullname', aliases=['alias'])
|
||||
self.assertRaises(argparse.ArgumentError,
|
||||
sp.add_parser, 'fullname')
|
||||
self.assertRaises(argparse.ArgumentError,
|
||||
sp.add_parser, 'alias')
|
||||
self.assertRaises(argparse.ArgumentError,
|
||||
sp.add_parser, 'other', aliases=['fullname'])
|
||||
self.assertRaises(argparse.ArgumentError,
|
||||
sp.add_parser, 'other', aliases=['alias'])
|
||||
|
||||
|
||||
# =============================
|
||||
# Help and Version option tests
|
||||
|
||||
Reference in new issue
Block a user