Skip to content

[3.13] gh-58282: Fix support of tuple metavar for positional arguments in argparse (GH-124782) #124882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,7 @@ def _format_action(self, action):
def _format_action_invocation(self, action):
if not action.option_strings:
default = self._get_default_metavar_for_positional(action)
metavar, = self._metavar_formatter(action, default)(1)
return metavar
return ' '.join(self._metavar_formatter(action, default)(1))

else:

Expand Down Expand Up @@ -703,7 +702,15 @@ def _get_action_name(argument):
elif argument.option_strings:
return '/'.join(argument.option_strings)
elif argument.metavar not in (None, SUPPRESS):
return argument.metavar
metavar = argument.metavar
if not isinstance(metavar, tuple):
return metavar
if argument.nargs == ZERO_OR_MORE and len(metavar) == 2:
return '%s[, %s]' % metavar
elif argument.nargs == ONE_OR_MORE:
return '%s[, %s]' % metavar
else:
return ', '.join(metavar)
elif argument.dest not in (None, SUPPRESS):
return argument.dest
elif argument.choices:
Expand Down
98 changes: 97 additions & 1 deletion Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4893,7 +4893,7 @@ class TestHelpNone(HelpTestCase):
version = ''


class TestHelpTupleMetavar(HelpTestCase):
class TestHelpTupleMetavarOptional(HelpTestCase):
"""Test specifying metavar as a tuple"""

parser_signature = Sig(prog='PROG')
Expand All @@ -4920,6 +4920,34 @@ class TestHelpTupleMetavar(HelpTestCase):
version = ''


class TestHelpTupleMetavarPositional(HelpTestCase):
"""Test specifying metavar on a Positional as a tuple"""

parser_signature = Sig(prog='PROG')
argument_signatures = [
Sig('w', help='w help', nargs='+', metavar=('W1', 'W2')),
Sig('x', help='x help', nargs='*', metavar=('X1', 'X2')),
Sig('y', help='y help', nargs=3, metavar=('Y1', 'Y2', 'Y3')),
Sig('z', help='z help', nargs='?', metavar=('Z1',)),
]
argument_group_signatures = []
usage = '''\
usage: PROG [-h] W1 [W2 ...] [X1 [X2 ...]] Y1 Y2 Y3 [Z1]
'''
help = usage + '''\

positional arguments:
W1 W2 w help
X1 X2 x help
Y1 Y2 Y3 y help
Z1 z help

options:
-h, --help show this help message and exit
'''
version = ''


class TestHelpRawText(HelpTestCase):
"""Test the RawTextHelpFormatter"""

Expand Down Expand Up @@ -6516,6 +6544,27 @@ def test_required_args(self):
'the following arguments are required: bar, baz$',
self.parser.parse_args, [])

def test_required_args_with_metavar(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', metavar='BaZ')
self.assertRaisesRegex(argparse.ArgumentError,
'the following arguments are required: bar, BaZ$',
self.parser.parse_args, [])

def test_required_args_n(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', nargs=3)
self.assertRaisesRegex(argparse.ArgumentError,
'the following arguments are required: bar, baz$',
self.parser.parse_args, [])

def test_required_args_n_with_metavar(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', nargs=3, metavar=('B', 'A', 'Z'))
self.assertRaisesRegex(argparse.ArgumentError,
'the following arguments are required: bar, B, A, Z$',
self.parser.parse_args, [])

def test_required_args_optional(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', nargs='?')
Expand All @@ -6530,6 +6579,20 @@ def test_required_args_zero_or_more(self):
'the following arguments are required: bar$',
self.parser.parse_args, [])

def test_required_args_one_or_more(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', nargs='+')
self.assertRaisesRegex(argparse.ArgumentError,
'the following arguments are required: bar, baz$',
self.parser.parse_args, [])

def test_required_args_one_or_more_with_metavar(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', nargs='+', metavar=('BaZ1', 'BaZ2'))
self.assertRaisesRegex(argparse.ArgumentError,
r'the following arguments are required: bar, BaZ1\[, BaZ2]$',
self.parser.parse_args, [])

def test_required_args_remainder(self):
self.parser.add_argument('bar')
self.parser.add_argument('baz', nargs='...')
Expand All @@ -6545,6 +6608,39 @@ def test_required_mutually_exclusive_args(self):
'one of the arguments --bar --baz is required',
self.parser.parse_args, [])

def test_conflicting_mutually_exclusive_args_optional_with_metavar(self):
group = self.parser.add_mutually_exclusive_group()
group.add_argument('--bar')
group.add_argument('baz', nargs='?', metavar='BaZ')
self.assertRaisesRegex(argparse.ArgumentError,
'argument BaZ: not allowed with argument --bar$',
self.parser.parse_args, ['--bar', 'a', 'b'])
self.assertRaisesRegex(argparse.ArgumentError,
'argument --bar: not allowed with argument BaZ$',
self.parser.parse_args, ['a', '--bar', 'b'])

def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar1(self):
group = self.parser.add_mutually_exclusive_group()
group.add_argument('--bar')
group.add_argument('baz', nargs='*', metavar=('BAZ1',))
self.assertRaisesRegex(argparse.ArgumentError,
'argument BAZ1: not allowed with argument --bar$',
self.parser.parse_args, ['--bar', 'a', 'b'])
self.assertRaisesRegex(argparse.ArgumentError,
'argument --bar: not allowed with argument BAZ1$',
self.parser.parse_args, ['a', '--bar', 'b'])

def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self):
group = self.parser.add_mutually_exclusive_group()
group.add_argument('--bar')
group.add_argument('baz', nargs='*', metavar=('BAZ1', 'BAZ2'))
self.assertRaisesRegex(argparse.ArgumentError,
r'argument BAZ1\[, BAZ2]: not allowed with argument --bar$',
self.parser.parse_args, ['--bar', 'a', 'b'])
self.assertRaisesRegex(argparse.ArgumentError,
r'argument --bar: not allowed with argument BAZ1\[, BAZ2]$',
self.parser.parse_args, ['a', '--bar', 'b'])

def test_ambiguous_option(self):
self.parser.add_argument('--foobaz')
self.parser.add_argument('--fooble', action='store_true')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :mod:`argparse` metavar processing to allow positional arguments to have a
tuple metavar.
Loading