mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +00:00
[3.13] gh-63143: Fix parsing mutually exclusive arguments in argparse (GH-124307) (GH-124418)
Arguments with the value identical to the default value (e.g. booleans,
small integers, empty or 1-character strings) are no longer considered
"not present".
(cherry picked from commit 3094cd17b0
)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
63870162f4
commit
167d8d2f07
3 changed files with 120 additions and 9 deletions
|
@ -1977,9 +1977,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
argument_values = self._get_values(action, argument_strings)
|
argument_values = self._get_values(action, argument_strings)
|
||||||
|
|
||||||
# error if this argument is not allowed with other previously
|
# error if this argument is not allowed with other previously
|
||||||
# seen arguments, assuming that actions that use the default
|
# seen arguments
|
||||||
# value don't really count as "present"
|
if action.option_strings or argument_strings:
|
||||||
if argument_values is not action.default:
|
|
||||||
seen_non_default_actions.add(action)
|
seen_non_default_actions.add(action)
|
||||||
for conflict_action in action_conflicts.get(action, []):
|
for conflict_action in action_conflicts.get(action, []):
|
||||||
if conflict_action in seen_non_default_actions:
|
if conflict_action in seen_non_default_actions:
|
||||||
|
|
|
@ -2900,26 +2900,30 @@ class MEMixin(object):
|
||||||
parse_args = self.get_parser(required=False).parse_args
|
parse_args = self.get_parser(required=False).parse_args
|
||||||
error = ArgumentParserError
|
error = ArgumentParserError
|
||||||
for args_string in self.failures:
|
for args_string in self.failures:
|
||||||
self.assertRaises(error, parse_args, args_string.split())
|
with self.subTest(args=args_string):
|
||||||
|
self.assertRaises(error, parse_args, args_string.split())
|
||||||
|
|
||||||
def test_failures_when_required(self):
|
def test_failures_when_required(self):
|
||||||
parse_args = self.get_parser(required=True).parse_args
|
parse_args = self.get_parser(required=True).parse_args
|
||||||
error = ArgumentParserError
|
error = ArgumentParserError
|
||||||
for args_string in self.failures + ['']:
|
for args_string in self.failures + ['']:
|
||||||
self.assertRaises(error, parse_args, args_string.split())
|
with self.subTest(args=args_string):
|
||||||
|
self.assertRaises(error, parse_args, args_string.split())
|
||||||
|
|
||||||
def test_successes_when_not_required(self):
|
def test_successes_when_not_required(self):
|
||||||
parse_args = self.get_parser(required=False).parse_args
|
parse_args = self.get_parser(required=False).parse_args
|
||||||
successes = self.successes + self.successes_when_not_required
|
successes = self.successes + self.successes_when_not_required
|
||||||
for args_string, expected_ns in successes:
|
for args_string, expected_ns in successes:
|
||||||
actual_ns = parse_args(args_string.split())
|
with self.subTest(args=args_string):
|
||||||
self.assertEqual(actual_ns, expected_ns)
|
actual_ns = parse_args(args_string.split())
|
||||||
|
self.assertEqual(actual_ns, expected_ns)
|
||||||
|
|
||||||
def test_successes_when_required(self):
|
def test_successes_when_required(self):
|
||||||
parse_args = self.get_parser(required=True).parse_args
|
parse_args = self.get_parser(required=True).parse_args
|
||||||
for args_string, expected_ns in self.successes:
|
for args_string, expected_ns in self.successes:
|
||||||
actual_ns = parse_args(args_string.split())
|
with self.subTest(args=args_string):
|
||||||
self.assertEqual(actual_ns, expected_ns)
|
actual_ns = parse_args(args_string.split())
|
||||||
|
self.assertEqual(actual_ns, expected_ns)
|
||||||
|
|
||||||
def test_usage_when_not_required(self):
|
def test_usage_when_not_required(self):
|
||||||
format_usage = self.get_parser(required=False).format_usage
|
format_usage = self.get_parser(required=False).format_usage
|
||||||
|
@ -3306,6 +3310,111 @@ class TestMutuallyExclusiveNested(MEMixin, TestCase):
|
||||||
test_successes_when_not_required = None
|
test_successes_when_not_required = None
|
||||||
test_successes_when_required = None
|
test_successes_when_required = None
|
||||||
|
|
||||||
|
|
||||||
|
class TestMutuallyExclusiveOptionalOptional(MEMixin, TestCase):
|
||||||
|
def get_parser(self, required=None):
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
group = parser.add_mutually_exclusive_group(required=required)
|
||||||
|
group.add_argument('--foo')
|
||||||
|
group.add_argument('--bar', nargs='?')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
failures = [
|
||||||
|
'--foo X --bar Y',
|
||||||
|
'--foo X --bar',
|
||||||
|
]
|
||||||
|
successes = [
|
||||||
|
('--foo X', NS(foo='X', bar=None)),
|
||||||
|
('--bar X', NS(foo=None, bar='X')),
|
||||||
|
('--bar', NS(foo=None, bar=None)),
|
||||||
|
]
|
||||||
|
successes_when_not_required = [
|
||||||
|
('', NS(foo=None, bar=None)),
|
||||||
|
]
|
||||||
|
usage_when_required = '''\
|
||||||
|
usage: PROG [-h] (--foo FOO | --bar [BAR])
|
||||||
|
'''
|
||||||
|
usage_when_not_required = '''\
|
||||||
|
usage: PROG [-h] [--foo FOO | --bar [BAR]]
|
||||||
|
'''
|
||||||
|
help = '''\
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--foo FOO
|
||||||
|
--bar [BAR]
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class TestMutuallyExclusiveOptionalWithDefault(MEMixin, TestCase):
|
||||||
|
def get_parser(self, required=None):
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
group = parser.add_mutually_exclusive_group(required=required)
|
||||||
|
group.add_argument('--foo')
|
||||||
|
group.add_argument('--bar', type=bool, default=True)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
failures = [
|
||||||
|
'--foo X --bar Y',
|
||||||
|
'--foo X --bar=',
|
||||||
|
]
|
||||||
|
successes = [
|
||||||
|
('--foo X', NS(foo='X', bar=True)),
|
||||||
|
('--bar X', NS(foo=None, bar=True)),
|
||||||
|
('--bar=', NS(foo=None, bar=False)),
|
||||||
|
]
|
||||||
|
successes_when_not_required = [
|
||||||
|
('', NS(foo=None, bar=True)),
|
||||||
|
]
|
||||||
|
usage_when_required = '''\
|
||||||
|
usage: PROG [-h] (--foo FOO | --bar BAR)
|
||||||
|
'''
|
||||||
|
usage_when_not_required = '''\
|
||||||
|
usage: PROG [-h] [--foo FOO | --bar BAR]
|
||||||
|
'''
|
||||||
|
help = '''\
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--foo FOO
|
||||||
|
--bar BAR
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class TestMutuallyExclusivePositionalWithDefault(MEMixin, TestCase):
|
||||||
|
def get_parser(self, required=None):
|
||||||
|
parser = ErrorRaisingArgumentParser(prog='PROG')
|
||||||
|
group = parser.add_mutually_exclusive_group(required=required)
|
||||||
|
group.add_argument('--foo')
|
||||||
|
group.add_argument('bar', nargs='?', type=bool, default=True)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
failures = [
|
||||||
|
'--foo X Y',
|
||||||
|
]
|
||||||
|
successes = [
|
||||||
|
('--foo X', NS(foo='X', bar=True)),
|
||||||
|
('X', NS(foo=None, bar=True)),
|
||||||
|
]
|
||||||
|
successes_when_not_required = [
|
||||||
|
('', NS(foo=None, bar=True)),
|
||||||
|
]
|
||||||
|
usage_when_required = '''\
|
||||||
|
usage: PROG [-h] (--foo FOO | bar)
|
||||||
|
'''
|
||||||
|
usage_when_not_required = '''\
|
||||||
|
usage: PROG [-h] [--foo FOO | bar]
|
||||||
|
'''
|
||||||
|
help = '''\
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
bar
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--foo FOO
|
||||||
|
'''
|
||||||
|
|
||||||
# =================================================
|
# =================================================
|
||||||
# Mutually exclusive group in parent parser tests
|
# Mutually exclusive group in parent parser tests
|
||||||
# =================================================
|
# =================================================
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Fix parsing mutually exclusive arguments in :mod:`argparse`. Arguments with
|
||||||
|
the value identical to the default value (e.g. booleans, small integers,
|
||||||
|
empty or 1-character strings) are no longer considered "not present".
|
Loading…
Add table
Add a link
Reference in a new issue