8000 gh-72795: Make positional arguments with nargs='*' or REMAINDER non-r… · python/cpython@a184a52 · GitHub
[go: up one dir, main page]

Skip to content

Commit a184a52

Browse files
gh-72795: Make positional arguments with nargs='*' or REMAINDER non-required
This allows to use positional argument with nargs='*' and without default in mutually exclusive group and improves error message about required arguments.
1 parent 342e654 commit a184a52

File tree

3 files changed

+43
-16
lines changed

3 files changed

+43
-16
lines changed

Lib/argparse.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,9 +1533,8 @@ def _get_positional_kwargs(self, dest, **kwargs):
15331533

15341534
# mark positional arguments as required if at least one is
15351535
# always required
1536-
if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
1537-
kwargs['required'] = True
1538-
if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
1536+
nargs = kwargs.get('nargs')
1537+
if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
15391538
kwargs['required'] = True
15401539

15411540
# return the keyword arguments with no option strings
@@ -1950,9 +1949,8 @@ def take_action(action, argument_strings, option_string=None):
19501949
argument_values = self._get_values(action, argument_strings)
19511950

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

Lib/test/test_argparse.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,26 +2877,30 @@ def test_failures_when_not_required(self):
28772877
parse_args = self.get_parser(required=False).parse_args
28782878
error = ArgumentParserError
28792879
for args_string in self.failures:
2880-
self.assertRaises(error, parse_args, args_string.split())
2880+
with self.subTest(args=args_string):
2881+
self.assertRaises(error, parse_args, args_string.split())
28812882

28822883
def test_failures_when_required(self):
28832884
parse_args = self.get_parser(required=True).parse_args
28842885
error = ArgumentParserError
28852886
for args_string in self.failures + ['']:
2886-
self.assertRaises(error, parse_args, args_string.split())
2887+
with self.subTest(args=args_string):
2888+
self.assertRaises(error, parse_args, args_string.split())
28872889

28882890
def test_successes_when_not_required(self):
28892891
parse_args = self.get_parser(required=False).parse_args
28902892
successes = self.successes + self.successes_when_not_required
28912893
for args_string, expected_ns in successes:
2892-
actual_ns = parse_args(args_string.split())
2893-
self.assertEqual(actual_ns, expected_ns)
2894+
with self.subTest(args=args_string):
2895+
actual_ns = parse_args(args_string.split())
2896+
10000 self.assertEqual(actual_ns, expected_ns)
28942897

28952898
def test_successes_when_required(self):
28962899
parse_args = self.get_parser(required=True).parse_args
28972900
for args_string, expected_ns in self.successes:
2898-
actual_ns = parse_args(args_string.split())
2899-
self.assertEqual(actual_ns, expected_ns)
2901+
with self.subTest(args=args_string):
2902+
actual_ns = parse_args(args_string.split())
2903+
self.assertEqual(actual_ns, expected_ns)
29002904

29012905
def test_usage_when_not_required(self):
29022906
format_usage = self.get_parser(required=False).format_usage
@@ -3073,7 +3077,7 @@ def get_parser(self, required):
30733077
group = parser.add_mutually_exclusive_group(required=required)
30743078
group.add_argument('--foo', action='store_true', help='FOO')
30753079
group.add_argument('--spam', help='SPAM')
3076-
group.add_argument('badger', nargs='*', default='X', help='BADGER')
3080+
group.add_argument('badger', nargs='*', help='BADGER')
30773081
return parser
30783082

30793083
failures = [
@@ -3084,13 +3088,13 @@ def get_parser(self, required):
30843088
'--foo X Y',
30853089
]
30863090
successes = [
3087-
('--foo', NS(foo=True, spam=None, badger='X')),
3088-
('--spam S', NS(foo=False, spam='S', badger='X')),
3091+
('--foo', NS(foo=True, spam=None, badger=[])),
3092+
('--spam S', NS(foo=False, spam='S', badger=[])),
30893093
('X', NS(foo=False, spam=None, badger=['X'])),
30903094
('X Y Z', NS(foo=False, spam=None, badger=['X', 'Y', 'Z'])),
30913095
]
30923096
successes_when_not_required = [
3093-
('', NS(foo=False, spam=None, badger='X')),
3097+
('', NS(foo=False, spam=None, badger=[])),
30943098
]
30953099

30963100
usage_when_not_required = '''\
@@ -6207,6 +6211,27 @@ def test_required_args(self):
62076211
'the following arguments are required: bar, baz',
62086212
self.parser.parse_args, [])
62096213

6214+
def test_required_args_optional(self):
6215+
self.parser.add_argument('bar')
6216+
self.parser.add_argument('baz', nargs='?')
6217+
self.assertRaisesRegex(argparse.ArgumentError,
6218+
'the following arguments are required: bar',
6219+
self.parser.parse_args, [])
6220+
6221+
def test_required_args_zero_or_more(self):
6222+
self.parser.add_argument('bar')
6223+
self.parser.add_argument('baz', nargs='*')
6224+
self.assertRaisesRegex(argparse.ArgumentError,
6225+
'the following arguments are required: bar',
6226+
self.parser.parse_args, [])
6227+
6228+
def test_required_args_remainder(self):
6229+
self.parser.add_argument('bar')
6230+
self.parser.add_argument('baz', nargs='...')
6231+
self.assertRaisesRegex(argparse.ArgumentError,
6232+
'the following arguments are required: bar',
6233+
self.parser.parse_args, [])
6234+
62106235
def test_required_mutually_exclusive_args(self):
62116236
group = self.parser.add_mutually_exclusive_group(required=True)
62126237
group.add_argument('--bar')
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Positional arguments with :ref:`nargs` equal to ``'*'`` or
2+
:data:`!argparse.REMAINDER` are no longer required. This allows to use
3+
positional argument with ``nargs='*'`` and without ``default`` in mutually
4+
exclusive group and improves error message about required arguments.

0 commit comments

Comments
 (0)
0