Skip to content

Commit

Permalink
gh-86463: Fix default prog in subparsers if usage is used in the main…
Browse files Browse the repository at this point in the history
… parser

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.
  • Loading branch information
serhiy-storchaka committed Oct 23, 2024
1 parent 834ba5a commit 9654987
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 5 deletions.
5 changes: 5 additions & 0 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ 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, consider
also passing the ``prog`` argument to :meth:`~ArgumentParser.add_subparsers`
or the ``prog`` and the ``usage`` arguments to
:meth:`~_SubParsersAction.add_parser`.


.. _description:

Expand Down
2 changes: 1 addition & 1 deletion Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1889,7 +1889,7 @@ def add_subparsers(self, **kwargs):
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
Expand Down
51 changes: 47 additions & 4 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -2388,16 +2388,17 @@ def assertArgumentParserError(self, *args, **kwargs):
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(
Expand Down Expand Up @@ -2434,7 +2435,8 @@ def _get_parser(self, subparser_help=False, prefix_chars=None,
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)
Expand All @@ -2456,6 +2458,47 @@ def test_parse_args_failures(self):
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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The ``usage`` parameter of :class:`argparse.ArgumentParser` no longer
affects the default value of the ``prog`` parameter in subparsers.

0 comments on commit 9654987

Please sign in to comment.