8000 gh-63143: Fix parsing mutually exclusive arguments in argparse (GH-12… · python/cpython@3094cd1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3094cd1

Browse files
gh-63143: Fix parsing mutually exclusive arguments in argparse (GH-124307)
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".
1 parent faef3fa commit 3094cd1

File tree

3 files changed

+120
-9
lines changed

3 files changed

+120
-9
lines changed

Lib/argparse.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,9 +1949,8 @@ def take_action(action, argument_strings, option_string=None):
19491949
argument_values = self._get_values(action, argument_strings)
19501950

19511951
# error if this argument is not allowed with other previously
1952-
# seen arguments, assuming that actions that use the default
1953-
# value don't really count as "present"
1954-
if argument_values is not action.default:
1952+
# seen arguments
1953+
if action.option_strings or argument_strings:
19551954
seen_non_default_actions.add(action)
19561955
for conflict_action in action_conflicts.get(action, []):
19571956
if conflict_action in seen_non_default_actions:

Lib/test/test_argparse.py

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,26 +2879,30 @@ def test_failures_when_not_required(self):
28792879
parse_args = self.get_parser(required=False).parse_args
28802880
error = ArgumentParserError
28812881
for args_string in self.failures:
2882-
self.assertRaises(error, parse_args, args_string.split())
2882+
with self.subTest(args=args_string):
2883+
self.assertRaises(error, parse_args, args_string.split())
28832884

28842885
def test_failures_when_required(self):
28852886
parse_args = self.get_parser(required=True).parse_args
28862887
error = ArgumentParserError
28872888
for args_string in self.failures + ['']:
2888-
self.assertRaises(error, parse_args, args_string.split())
2889+
with self.subTest(args=args_string):
2890+
self.assertRaises(error, parse_args, args_string.split())
28892891

28902892
def test_successes_when_not_required(self):
28912893
parse_args = self.get_parser(required=False).parse_args
28922894
successes = self.successes + self.successes_when_not_required
28932895
for args_string, expected_ns in successes:
2894-
actual_ns = parse_args(args_string.split())
2895-
self.assertEqual(actual_ns, expected_ns)
2896+
with self.subTest(args=args_string):
2897+
actual_ns = parse_args(args_string.split())
2898+
self.assertEqual(actual_ns, expected_ns)
28962899

28972900
def test_successes_when_required(self):
28982901
parse_args = self.get_parser(required=True).parse_args
28992902
for args_string, expected_ns in self.successes:
2900-
actual_ns = parse_args(args_string.split())
2901-
self.assertEqual(actual_ns, expected_ns)
2903+
with self.subTest(args=args_string):
2904+
actual_ns = parse_args(args_string.split())
2905+
self.assertEqual(actual_ns, expected_ns)
29022906

29032907
def test_usage_when_not_required(self):
29042908
format_usage = self.get_parser(required=False).format_usage
@@ -3285,6 +3289,111 @@ def get_parser(self, required):
32853289
test_successes_when_not_required = None
32863290
test_successes_when_required = None
32873291

3292+
3293+
class TestMutuallyExclusiveOptionalOptional(MEMixin, TestCase):
3294+
def get_parser(self, required=None):
3295+
parser = ErrorRaisingArgumentParser(prog='PROG')
3296+
group = parser.add_mutually_exclusive_group(required=required)
3297+
group.add_argument('--foo')
3298+
group.add_argument('--bar', nargs='?')
3299+
return parser
3300+
3301+
failures = [
3302+
'--foo X --bar Y',
3303+
'--foo X --bar',
3304+
]
3305+
successes = [
3306+
('--foo X', NS(foo='X', bar=None)),
3307+
('--bar X', NS(foo=None, bar='X')),
3308+
('--bar', NS(foo=None, bar=None)),
3309+
]
3310+
successes_when_not_required = [
3311+
('', NS(foo=None, bar=None)),
3312+
]
3313+
usage_when_required = '''\
3314+
usage: PROG [-h] (--foo FOO | --bar [BAR])
3315+
'''
3316+
usage_when_not_required = '''\
3317+
usage: PROG [-h] [--foo FOO | --bar [BAR]]
3318+
'''
3319+
help = '''\
3320+
3321+
options:
3322+
-h, --help show this help message and exit
3323+
--foo FOO
3324+
--bar [BAR]
3325+
'''
3326+
3327+
3328+
class TestMutuallyExclusiveOptionalWithDefault(MEMixin, TestCase):
3329+
def get_parser(self, required=None):
3330+
parser = ErrorRaisingArgumentParser(prog='PROG')
3331+
group = parser.add_mutually_exclusive_group(required=required)
3332+
group.add_argument('--foo')
3333+
group.add_argument('--bar', type=bool, default=True)
3334+
return parser
3335+
3336+
failures = [
3337+
'--foo X --bar Y',
3338+
'--foo X --bar=',
3339+
]
3340+
successes = [
3341+
('--foo X', NS(foo='X', bar=True)),
3342+
('--bar X', NS(foo=None, bar=True)),
3343+
('--bar=', NS(foo=None, bar=False)),
3344+
]
3345+
successes_when_not_required = [
3346+
('', NS(foo=None, bar=True)),
3347+
]
3348+
usage_when_required = '''\
3349+
usage: PROG [-h] (--foo FOO | --bar BAR)
3350+
'''
3351+
usage_when_not_required = '''\
3352+
usage: PROG [-h] [--foo FOO | --bar BAR]
3353+
'''
3354+
help = '''\
3355+
3356+
options:
3357+
-h, --help show this help message and exit
3358+
--foo FOO
3359+
--bar BAR
3360+
'''
3361+
3362+
3363+
class TestMutuallyExclusivePositionalWithDefault(MEMixin, TestCase):
3364+
def get_parser(self, required=None):
3365+
parser = ErrorRaisingArgumentParser(prog='PROG')
3366+
group = parser.add_mutually_exclusive_group(required=required)
3367+
group.add_argument('--foo')
3368+
group.add_argument('bar', nargs='?', type=bool, default=True)
3369+
return parser
3370+
3371+
failures = [
3372+
'--foo X Y',
3373+
]
3374+
successes = [
3375+
('--foo X', NS(foo='X', bar=True)),
3376+
('X', NS(foo=None, bar=True)),
3377+
]
3378+
successes_when_not_required = [
3379+
('', NS(foo=None, bar=True)),
3380+
]
3381+
usage_when_required = '''\
3382+
usage: PROG [-h] (--foo FOO | bar)
3383+
'''
3384+
usage_when_not_required = '''\
3385+
usage: PROG [-h] [--foo FOO | bar]
3386+
'''
3387+
help = '''\
3388+
3389+
positional arguments:
3390+
bar
3391+
3392+
options:
3393+
-h, --help show this help message and exit
3394+
--foo FOO
3395+
'''
3396+
32883397
# =================================================
32893398
# Mutually exclusive group in parent parser tests
32903399
# =================================================
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix parsing mutually exclusive arguments in :mod:`argparse`. Arguments with
2+
the value identical to the default value (e.g. booleans, small integers,
3+
empty or 1-character strings) are no longer considered "not present".

0 commit comments

Comments
 (0)
0