Skip to content

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

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
10 changes: 10 additions & 0 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was merged with a failing lint check on the CI.

https://github.com/python/cpython/actions/runs/11701457525/job/32587601941

Please could you trim the trailing space here?

-:meth:`~_SubParsersAction.add_parser`, to ensure consistent command prefixes and 
+:meth:`~_SubParsersAction.add_parser`, to ensure consistent command prefixes and

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will not be available in the next 2 hours.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usage information across subparsers.


.. _description:

Expand Down Expand Up @@ -1808,6 +1814,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
^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1886,7 +1886,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 @@ -2408,16 +2408,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 @@ -2454,7 +2455,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 @@ -2476,6 +2478,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.
Loading