https://github.com/python/cpython/commit/0cb4d6c6549d2299f7518f083bbe7d10314ecd66
commit: 0cb4d6c6549d2299f7518f083bbe7d10314ecd66
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-11-22T17:29:33+02:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2024-10-23-20-05-54.gh-issue-86463.jvFTI_.rst
M Doc/library/argparse.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index a4695547921faa..c5882c273717c0 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -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
 ^^^^^^^^^^^^^^^^
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 5ecfdca17175e3..f5a7342c2fc355 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 358cfb1c56aae4..69243fde5f0f98 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -2409,16 +2409,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(
@@ -2455,7 +2456,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)
@@ -2477,6 +2479,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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to