gh-86463: Fix default prog in subparsers if usage is used in the main parser (GH-125891)

The usage parameter of argparse.ArgumentParser no longer
affects the default value of the prog parameter in subparsers.

Previously the full custom usage of the main parser was used as
the prog prefix in subparsers.
This commit is contained in:
Serhiy Storchaka 2024-11-22 17:29:33 +02:00 committed by GitHub
parent 46f8a7bbdb
commit 0cb4d6c654
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 5 deletions

View File

@ -192,6 +192,12 @@ arguments it contains. The default message can be overridden with the
The ``%(prog)s`` format specifier is available to fill in the program name in
your usage messages.
When a custom usage message is specified for the main parser, you may also want to
consider passing the ``prog`` argument to :meth:`~ArgumentParser.add_subparsers`
or the ``prog`` and the ``usage`` arguments to
:meth:`~_SubParsersAction.add_parser`, to ensure consistent command prefixes and
usage information across subparsers.
.. _description:
@ -1810,6 +1816,10 @@ Sub-commands
.. versionchanged:: 3.7
New *required* keyword-only parameter.
.. versionchanged:: 3.14
Subparser's *prog* is no longer affected by a custom usage message in
the main parser.
FileType objects
^^^^^^^^^^^^^^^^

View File

@ -1889,7 +1889,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
formatter = self._get_formatter()
positionals = self._get_positional_actions()
groups = self._mutually_exclusive_groups
formatter.add_usage(self.usage, positionals, groups, '')
formatter.add_usage(None, positionals, groups, '')
kwargs['prog'] = formatter.format_help().strip()
# create the parsers action and add it to the positionals list

View File

@ -2409,16 +2409,17 @@ class TestAddSubparsers(TestCase):
self.assertRaises(ArgumentParserError, *args, **kwargs)
def _get_parser(self, subparser_help=False, prefix_chars=None,
aliases=False):
aliases=False, usage=None):
# create a parser with a subparsers argument
if prefix_chars:
parser = ErrorRaisingArgumentParser(
prog='PROG', description='main description', prefix_chars=prefix_chars)
prog='PROG', description='main description', usage=usage,
prefix_chars=prefix_chars)
parser.add_argument(
prefix_chars[0] * 2 + 'foo', action='store_true', help='foo help')
else:
parser = ErrorRaisingArgumentParser(
prog='PROG', description='main description')
prog='PROG', description='main description', usage=usage)
parser.add_argument(
'--foo', action='store_true', help='foo help')
parser.add_argument(
@ -2455,7 +2456,8 @@ class TestAddSubparsers(TestCase):
parser2.add_argument('z', type=complex, nargs='*', help='z help')
# add third sub-parser
parser3_kwargs = dict(description='3 description')
parser3_kwargs = dict(description='3 description',
usage='PROG --foo bar 3 t ...')
if subparser_help:
parser3_kwargs['help'] = '3 help'
parser3 = subparsers.add_parser('3', **parser3_kwargs)
@ -2477,6 +2479,47 @@ class TestAddSubparsers(TestCase):
args = args_str.split()
self.assertArgumentParserError(self.parser.parse_args, args)
def test_parse_args_failures_details(self):
for args_str, usage_str, error_str in [
('',
'usage: PROG [-h] [--foo] bar {1,2,3} ...',
'PROG: error: the following arguments are required: bar'),
('0.5 1 -y',
'usage: PROG bar 1 [-h] [-w W] {a,b,c}',
'PROG bar 1: error: the following arguments are required: x'),
('0.5 3',
'usage: PROG --foo bar 3 t ...',
'PROG bar 3: error: the following arguments are required: t'),
]:
with self.subTest(args_str):
args = args_str.split()
with self.assertRaises(ArgumentParserError) as cm:
self.parser.parse_args(args)
self.assertEqual(cm.exception.args[0], 'SystemExit')
self.assertEqual(cm.exception.args[2], f'{usage_str}\n{error_str}\n')
def test_parse_args_failures_details_custom_usage(self):
parser = self._get_parser(usage='PROG [--foo] bar 1 [-w W] {a,b,c}\n'
' PROG --foo bar 3 t ...')
for args_str, usage_str, error_str in [
('',
'usage: PROG [--foo] bar 1 [-w W] {a,b,c}\n'
' PROG --foo bar 3 t ...',
'PROG: error: the following arguments are required: bar'),
('0.5 1 -y',
'usage: PROG bar 1 [-h] [-w W] {a,b,c}',
'PROG bar 1: error: the following arguments are required: x'),
('0.5 3',
'usage: PROG --foo bar 3 t ...',
'PROG bar 3: error: the following arguments are required: t'),
]:
with self.subTest(args_str):
args = args_str.split()
with self.assertRaises(ArgumentParserError) as cm:
parser.parse_args(args)
self.assertEqual(cm.exception.args[0], 'SystemExit')
self.assertEqual(cm.exception.args[2], f'{usage_str}\n{error_str}\n')
def test_parse_args(self):
# check some non-failure cases:
self.assertEqual(

View File

@ -0,0 +1,2 @@
The ``usage`` parameter of :class:`argparse.ArgumentParser` no longer
affects the default value of the ``prog`` parameter in subparsers.