https://github.com/python/cpython/commit/f28409cb8ccf81b13bff1f2afedec0cb3a78732d
commit: f28409cb8ccf81b13bff1f2afedec0cb3a78732d
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: savannahostrowski <[email protected]>
date: 2025-12-06T15:35:01Z
summary:
[3.13] GH-75949: Fix argparse dropping '|' in mutually exclusive groups on line
wrap (GH-142312) (#142348)
files:
A Misc/NEWS.d/next/Library/2025-12-05-16-39-17.gh-issue-75949.pHxW98.rst
M Lib/argparse.py
M Lib/test/test_argparse.py
diff --git a/Lib/argparse.py b/Lib/argparse.py
index bd088ea0e66a04..adb35dd0180c0c 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -330,8 +330,14 @@ def _format_usage(self, usage, actions, groups, prefix):
if len(prefix) + len(usage) > text_width:
# break usage into wrappable parts
- opt_parts = self._get_actions_usage_parts(optionals, groups)
- pos_parts = self._get_actions_usage_parts(positionals, groups)
+ # keep optionals and positionals together to preserve
+ # mutually exclusive group formatting (gh-75949)
+ all_actions = optionals + positionals
+ parts, pos_start = self._get_actions_usage_parts_with_split(
+ all_actions, groups, len(optionals)
+ )
+ opt_parts = parts[:pos_start]
+ pos_parts = parts[pos_start:]
# helper for wrapping lines
def get_lines(parts, indent, prefix=None):
@@ -387,6 +393,17 @@ def _format_actions_usage(self, actions, groups):
return ' '.join(self._get_actions_usage_parts(actions, groups))
def _get_actions_usage_parts(self, actions, groups):
+ parts, _ = self._get_actions_usage_parts_with_split(actions, groups)
+ return parts
+
+ def _get_actions_usage_parts_with_split(self, actions, groups,
opt_count=None):
+ """Get usage parts with split index for optionals/positionals.
+
+ Returns (parts, pos_start) where pos_start is the index in parts
+ where positionals begin. When opt_count is None, pos_start is None.
+ This preserves mutually exclusive group formatting across the
+ optionals/positionals boundary (gh-75949).
+ """
# find group indices and identify actions in groups
group_actions = set()
inserts = {}
@@ -469,8 +486,16 @@ def _get_actions_usage_parts(self, actions, groups):
for i in range(start + group_size, end):
parts[i] = None
- # return the usage parts
- return [item for item in parts if item is not None]
+ # if opt_count is provided, calculate where positionals start in
+ # the final parts list (for wrapping onto separate lines).
+ # Count before filtering None entries since indices shift after.
+ if opt_count is not None:
+ pos_start = sum(1 for p in parts[:opt_count] if p is not None)
+ else:
+ pos_start = None
+
+ # return the usage parts and split point (gh-75949)
+ return [item for item in parts if item is not None], pos_start
def _format_text(self, text):
if '%(prog)' in text:
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index b7e995334fedb1..8974247e6e955e 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -4700,6 +4700,25 @@ def test_long_mutex_groups_wrap(self):
''')
self.assertEqual(parser.format_usage(), usage)
+ def test_mutex_groups_with_mixed_optionals_positionals_wrap(self):
+ # https://github.com/python/cpython/issues/75949
+ # Mutually exclusive groups containing both optionals and positionals
+ # should preserve pipe separators when the usage line wraps.
+ parser = argparse.ArgumentParser(prog='PROG')
+ g = parser.add_mutually_exclusive_group()
+ g.add_argument('-v', '--verbose', action='store_true')
+ g.add_argument('-q', '--quiet', action='store_true')
+ g.add_argument('-x', '--extra-long-option-name', nargs='?')
+ g.add_argument('-y', '--yet-another-long-option', nargs='?')
+ g.add_argument('positional', nargs='?')
+
+ usage = textwrap.dedent('''\
+ usage: PROG [-h] [-v | -q | -x [EXTRA_LONG_OPTION_NAME] |
+ -y [YET_ANOTHER_LONG_OPTION] |
+ positional]
+ ''')
+ self.assertEqual(parser.format_usage(), usage)
+
class TestHelpVariableExpansion(HelpTestCase):
"""Test that variables are expanded properly in help messages"""
diff --git
a/Misc/NEWS.d/next/Library/2025-12-05-16-39-17.gh-issue-75949.pHxW98.rst
b/Misc/NEWS.d/next/Library/2025-12-05-16-39-17.gh-issue-75949.pHxW98.rst
new file mode 100644
index 00000000000000..5ca3fc05b9816d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-05-16-39-17.gh-issue-75949.pHxW98.rst
@@ -0,0 +1 @@
+Fix :mod:`argparse` to preserve ``|`` separators in mutually exclusive groups
when the usage line wraps due to length.
_______________________________________________
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]