10000 [3.12] gh-58282: Fix support of tuple metavar for positional argument… · python/cpython@72bbebd · GitHub
[go: up one dir, main page]

Skip to content

Commit 72bbebd

Browse files
miss-islingtonserhiy-storchakacykerway
authored
[3.12] gh-58282: Fix support of tuple metavar for positional arguments in argparse (GH-124782) (GH-124881)
Previously, formatting help output or error message for positional argument with a tuple metavar raised exception. (cherry picked from commit 9b31a2d) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Cyker Way <cykerway@gmail.com>
1 parent b338a61 commit 72bbebd

File tree

3 files changed

+109
-4
lines changed

3 files changed

+109
-4
lines changed

Lib/argparse.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,7 @@ def _format_action(self, action):
564564
def _format_action_invocation(self, action):
565565
if not action.option_strings:
566566
default = self._get_default_metavar_for_positional(action)
567-
metavar, = self._metavar_formatter(action, default)(1)
568-
return metavar
567+
return ' '.join(self._metavar_formatter(action, default)(1))
569568

570569
else:
571570
parts = []
@@ -752,7 +751,15 @@ def _get_action_name(argument):
752751
elif argument.option_strings:
753752
return '/'.join(argument.option_strings)
754753
elif argument.metavar not in (None, SUPPRESS):
755-
return argument.metavar
754+
metavar = argument.metavar
755+
if not isinstance(metavar, tuple):
756+
return metavar
757+
if argument.nargs == ZERO_OR_MORE and len(metavar) == 2:
758+
return '%s[, %s]' % metavar
759+
elif argument.nargs == ONE_OR_MORE:
760+
return '%s[, %s]' % metavar
761+
else:
762+
return ', '.join(metavar)
756763
elif argument.dest not in (None, SUPPRESS):
757764
return argument.dest
758765
elif argument.choices:

Lib/test/test_argparse.py

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4674,7 +4674,7 @@ class TestHelpNone(HelpTestCase):
46744674
version = ''
46754675

46764676

4677-
class TestHelpTupleMetavar(HelpTestCase):
4677+
class TestHelpTupleMetavarOptional(HelpTestCase):
46784678
"""Test specifying metavar as a tuple"""
46794679

46804680
parser_signature = Sig(prog='PROG')
@@ -4701,6 +4701,34 @@ class TestHelpTupleMetavar(HelpTestCase):
47014701
version = ''
47024702

47034703

4704+
class TestHelpTupleMetavarPositional(HelpTestCase):
4705+
"""Test specifying metavar on a Positional as a tuple"""
4706+
4707+
parser_signature = Sig(prog='PROG')
4708+
argument_signatures = [
4709+
Sig('w', help='w help', nargs='+', metavar=('W1', 'W2')),
4710+
Sig('x', help='x help', nargs='*', metavar=('X1', 'X2')),
4711+
Sig('y', help='y help', nargs=3, metavar=('Y1', 'Y2', 'Y3')),
4712+
Sig('z', help='z help', nargs='?', metavar=('Z1',)),
4713+
]
4714+
argument_group_signatures = []
4715+
usage = '''\
4716+
usage: PROG [-h] W1 [W2 ...] [X1 [X2 ...]] Y1 Y2 Y3 [Z1]
4717+
'''
4718+
help = usage + '''\
4719+
4720+
positional arguments:
4721+
W1 W2 w help
4722+
X1 X2 x help
4723+
Y1 Y2 Y3 y help
4724+
Z1 z help
4725+
4726+
options:
4727+
-h, --help show this help message and exit
4728+
'''
4729+
version = ''
4730+
4731+
47044732
class TestHelpRawText(HelpTestCase):
47054733
"""Test the RawTextHelpFormatter"""
47064734

@@ -6146,6 +6174,27 @@ def test_required_args(self):
61466174
'the following arguments are required: bar, baz$',
61476175
self.parser.parse_args, [])
61486176

6177+
def test_required_args_with_metavar(self):
6178+
self.parser.add_argument('bar')
6179+
self.parser.add_argument('baz', metavar='BaZ')
6180+
self.assertRaisesRegex(argparse.ArgumentError,
6181+
'the following arguments are required: bar, BaZ$',
6182+
self.parser.parse_args, [])
6183+
6184+
def test_required_args_n(self):
6185+
self.parser.add_argument('bar')
6186+
self.parser.add_argument('baz', nargs=3)
6187+
self.assertRaisesRegex(argparse.ArgumentError,
6188+
'the following arguments are required: bar, baz$',
6189+
self.parser.parse_args, [])
6190+
6191+
def test_required_args_n_with_metavar(self):
6192+
self.parser.add_argument('bar')
6193+
self.parser.add_argument('baz', nargs=3, metavar=('B', 'A', 'Z'))
6194+
self.assertRaisesRegex(argparse.ArgumentError,
6195+
'the following arguments are required: bar, B, A, Z$',
6196+
self.parser.parse_args, [])
6197+
61496198
def test_required_args_optional(self):
61506199
self.parser.add_argument('bar')
61516200
self.parser.add_argument('baz', nargs='?')
@@ -6160,6 +6209,20 @@ def test_required_args_zero_or_more(self):
61606209
'the following arguments are required: bar$',
61616210
self.parser.parse_args, [])
61626211

6212+
def test_required_args_one_or_more(self):
6213+
self.parser.add_argument('bar')
6214+
self.parser.add_argument('baz', nargs='+')
6215+
self.assertRaisesRegex(argparse.ArgumentError,
6216+
'the following arguments are required: bar, baz$',
6217+
self.parser.parse_args, [])
6218+
6219+
def test_required_args_one_or_more_with_metavar(self):
6220+
self.parser.add_argument('bar')
6221+
self.parser.add_argument('baz', nargs='+', metavar=('BaZ1', 'BaZ2'))
6222+
self.assertRaisesRegex(argparse.ArgumentError,
6223+
r'the following arguments are required: bar, BaZ1\[, BaZ2]$',
6224+
self.parser.parse_args, [])
6225+
61636226
def test_required_args_remainder(self):
61646227
self.parser.add_argument('bar')
61656228
self.parser.add_argument('baz', nargs='...')
@@ -6175,6 +6238,39 @@ def test_required_mutually_exclusive_args(self):
61756238
'one of the arguments --bar --baz is required',
61766239
self.parser.parse_args, [])
61776240

6241+
def test_conflicting_mutually_exclusive_args_optional_with_metavar(self):
6242+
group = self.parser.add_mutually_exclusive_group()
6243+
group.add_argument('--bar')
6244+
group.add_argument('baz', nargs='?', metavar='BaZ')
6245+
self.assertRaisesRegex(argparse.ArgumentError,
6246+
'argument BaZ: not allowed with argument --bar$',
6247+
self.parser.parse_args, ['--bar', 'a', 'b'])
6248+
self.assertRaisesRegex(argparse.ArgumentError,
6249+
'argument --bar: not allowed with argument BaZ$',
6250+
self.parser.parse_args, ['a', '--bar', 'b'])
6251+
6252+
def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar1(self):
6253+
group = self.parser.add_mutually_exclusive_group()
6254+
group.add_argument('--bar')
6255+
group.add_argument('baz', nargs='*', metavar=('BAZ1',))
6256+
self.assertRaisesRegex(argparse.ArgumentError,
6257+
'argument BAZ1: not allowed with argument --bar$',
6258+
self.parser.parse_args, ['--bar', 'a', 'b'])
6259+
self.assertRaisesRegex(argparse.ArgumentError,
6260+
'argument --bar: not allowed with argument BAZ1$',
6261+
self.parser.parse_args, ['a', '--bar', 'b'])
6262+
6263+
def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self):
6264+
group = self.parser.add_mutually_exclusive_group()
6265+
group.add_argument('--bar')
6266+
group.add_argument('baz', nargs='*', metavar=('BAZ1', 'BAZ2'))
6267+
self.assertRaisesRegex(argparse.ArgumentError,
6268+
r'argument BAZ1\[, BAZ2]: not allowed with argument --bar$',
6269+
self.parser.parse_args, ['--bar', 'a', 'b'])
6270+
self.assertRaisesRegex(argparse.ArgumentError,
6271+
r'argument --bar: not allowed with argument BAZ1\[, BAZ2]$',
6272+
self.parser.parse_args, ['a', '--bar', 'b'])
6273+
61786274
def test_ambiguous_option(self):
61796275
self.parser.add_argument('--foobaz')
61806276
self.parser.add_argument('--fooble', action='store_true')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :mod:`argparse` metavar processing to allow positional arguments to have a
2+
tuple metavar.

0 commit comments

Comments
 (0)
0