From 9654987f8601859fd2a62b92140c33e56d97c170 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 25 Sep 2024 21:28:21 +0300 Subject: [PATCH 1/4] gh-86463: Fix default prog in subparsers if usage is used in the main 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. --- Doc/library/argparse.rst | 5 ++ Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 51 +++++++++++++++++-- ...4-10-23-20-05-54.gh-issue-86463.jvFTI_.rst | 2 + 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-23-20-05-54.gh-issue-86463.jvFTI_.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 65663d43f50a9d..2c9bf22a62cc0b 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -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: diff --git a/Lib/argparse.py b/Lib/argparse.py index 9746173984c6ca..1db39f82675391 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -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 diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index ed1c5c34e526aa..c9b3cbfcc694b6 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -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( @@ -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) @@ -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( diff --git a/Misc/NEWS.d/next/Library/2024-10-23-20-05-54.gh-issue-86463.jvFTI_.rst b/Misc/NEWS.d/next/Library/2024-10-23-20-05-54.gh-issue-86463.jvFTI_.rst new file mode 100644 index 00000000000000..9ac155770e2254 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-23-20-05-54.gh-issue-86463.jvFTI_.rst @@ -0,0 +1,2 @@ +The ``usage`` parameter of :class:`argparse.ArgumentParser` no longer +affects the default value of the ``prog`` parameter in subparsers. From a3fba3f6ed191244100a74b92313c06be0fed7c3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 5 Nov 2024 10:37:56 +0200 Subject: [PATCH 2/4] Add versionchanged. --- Doc/library/argparse.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 25fddca6c5dbd4..18957c62ccc2ba 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1813,6 +1813,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 ^^^^^^^^^^^^^^^^ From 533a07e35053cd45ae17ee1c18332d6ec3523c49 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 6 Nov 2024 12:11:40 +0200 Subject: [PATCH 3/4] Update Doc/library/argparse.rst Co-authored-by: Savannah Ostrowski --- Doc/library/argparse.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 18957c62ccc2ba..e6d94ad8bdbd5f 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -192,10 +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, consider -also passing the ``prog`` argument to :meth:`~ArgumentParser.add_subparsers` +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`. +:meth:`~_SubParsersAction.add_parser`, to ensure consistent command prefixes and +usage information across subparsers. +`` .. _description: From fd5743f8d0e297dd9c04b8ecd3d89db2151eb3e6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 6 Nov 2024 12:12:38 +0200 Subject: [PATCH 4/4] Update Doc/library/argparse.rst --- Doc/library/argparse.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index e6d94ad8bdbd5f..a2763748305c06 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -197,7 +197,6 @@ 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: