Repository navigation
Argparse wrapping is bugged when colors are involved #142035
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Nov 28, 2025 - addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Nov 28, 2025 - added3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes
on Nov 28, 2025 Looking a bit more at the code, I believe it would make more sense to add a parameter to
textwrapto ignore ANSI escape codes when wrapping/filling, then leverage it inargparse.With the recent addition of colors in the stdlib, I think it makes sense to have that option in textwrap directly. I would argue it should default to
True, but I will leave that to the experts 😉.I opened #142040 as a proof-of-concept, if this is viable for the code owners, I can add the tests, docs, etc.
EDIT: this is also an issue with multiple-code-points glyphs, textwrap is not handling them cleanly, so it can cause other wrapping issues:
>>> textwrap.wrap("Cafe\u0301 is good", width=4) ['Cafe', '́ is', 'good'] >>> textwrap.wrap("Cafe\u0301 is good", width=5) ['Café', 'is', 'good']
AFAICT there is no clean way in stdlib to get the actual rendering width of such strings, like what is done by third party libs like wcwidth.
Please can you share some example code that causes this?
Running on the current CPython main:
import argparse parser = argparse.ArgumentParser( description="A simple argument parser example.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("--verbose", action="store_true", help="A l o n g d e s c r i p t i o n f o r t h e v e r b o s e f l a g t o d e m o n s t r a t e t h e f o r m a t t e r c l a s s u s e d i n t h i s a r g u m e n t p a r s e r .") parser.add_argument("--input", type=str, default="input.txt", help="Input file path") args = parser.parse_args()
Then:
$ COLUMNS=70 python test.py --help usage: test.py [-h] [--verbose] [--input INPUT] A simple argument parser example. options: -h, --help show this help message and exit --verbose A l o n g d e s c r i p t i o n f o r t h e v e r b o s e f l a g t o d e m o n s t r a t e t h e f o r m a t t e r c l a s s u s e d i n t h i s a r g u m e n t p a r s e r . (default: False) --input INPUT Input file path (default: input.txt)You can see the
Falsefor--verboseis not wrapping correctly.
If you modify the script to manually write the default value, so it does not have the colors introduced in #141680import argparse parser = argparse.ArgumentParser( description="A simple argument parser example.", ) parser.add_argument("--verbose", action="store_true", help="A l o n g d e s c r i p t i o n f o r t h e v e r b o s e f l a g t o d e m o n s t r a t e t h e f o r m a t t e r c l a s s u s e d i n t h i s a r g u m e n t p a r s e r . (default: False)") parser.add_argument("--input", type=str, default="input.txt", help="Input file path") args = parser.parse_args()
Here is the text is wrapping correctly.
usage: test.py [-h] [--verbose] [--input INPUT] A simple argument parser example. options: -h, --help show this help message and exit --verbose A l o n g d e s c r i p t i o n f o r t h e v e r b o s e f l a g t o d e m o n s t r a t e t h e f o r m a t t e r c l a s s u s e d i n t h i s a r g u m e n t p a r s e r . (default: False) --input INPUT Input file path#142040 is one way to fix it.
Reacted by Hugo van KemenadeMaybe we could assume that
(default: ...)is actually added to the description text before we format the entire thing? It looks like we are not wrapping the same text.Looking a bit more at the code, I believe it would make more sense to add a parameter to
textwrapto ignore ANSI escape codes when wrapping/filling, then leverage it inargparse.With the recent addition of colors in the stdlib, I think it makes sense to have that option in textwrap directly. I would argue it should default to
True, but I will leave that to the experts 😉.I opened #142040 as a proof-of-concept, if this is viable for the code owners, I can add the tests, docs, etc.
EDIT: this is also an issue with multiple-code-points glyphs, textwrap is not handling them cleanly, so it can cause other wrapping issues:
textwrap.wrap("Cafe\u0301 is good", width=4)
['Cafe', '́ is', 'good']
textwrap.wrap("Cafe\u0301 is good", width=5)
['Café', 'is', 'good']
AFAICT there is no clean way in stdlib to get the actual rendering width of such strings, like what is done by third party libs like wcwidth.wcwidth in terminal is hard.....
pdm also found similar issue when color the arg when update to 3.14
info: pdm-project/pdm#3682For the record, as I explained in the linked PR, I believe the best solution to fix this would be to add a way to customize the length function of
textwrap, like what was proposed in #28136.
argparsecould then use this to avoid counting the escape codes when wrapping.Reacted by Savannah OstrowskiI would be happy to have a custom length because I needed this in Sphinx. If I have more time this week-end I'll try to ressurect that other PR and check whether we can do something with this. This is a bit unfortunate that this will only be present in 3.15+ though, considering it's kinda annoying in Python 3.14. It's very user-facing in general now that all CLIs are colored (I don't know if they are colored by default but I think so) so we should also find a way to make it work in 3.14.
Reacted by Alex PrengèreFWIW, text wrapping in argparse is quite fragile. There are a couple open issues related to text wrapping already open (albeit the solution would be different than what is required here). I do agree that it'd be nice to fix this in a more holistic way though.
Reacted by Alex PrengèreIf
textwrapever gets such a custom length parameter, that would make having a way to know multiple-code-points glyphs width even more appealing 😉, see #56777 for prior discussion.10 remaining items
The proposed textwrap change adds a public API, so it isn’t suitable for backporting. I’ve opened #154634 with a narrower, argparse-only bug fix that should be backportable. The broader textwrap API proposal should be considered separately.
Reacted by Hugo van Kemenade and Alex PrengèreThe proposed textwrap change adds a public API, so it isn’t suitable for backporting. I’ve opened #154634 with a narrower, argparse-only bug fix that should be backportable. The broader textwrap API proposal should be considered separately.
OK I will wait for feedback and eventually the merge of your #154634 PR before re-adapting my #152702 code to it.
- added 3 commits that reference this issue
on Jul 24, 2026 I'm going to close this issue since it's specifically about a bug in argparse and the corresponding PRs have been merged. @kdeldycke Can you open a new issue to track the feature request for textwrap?
Also removing the 3.14 label since AFAICT this only repros on 3.15+.
Reacted by Hugo van KemenadeI'm going to close this issue since it's specifically about a bug in argparse and the corresponding PRs have been merged. @kdeldycke Can you open a new issue to track the feature request for textwrap?
Just added an new issue at #154686 . This describe the situation and cross-link with my #152702 PR. The latter now removes your local patch and generalize it.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDoc issues
Bug report
Bug description:
While working on coloring interpolated values in #141940, I realized that in some cases the wrapping is broken.
For example in this case, just a color change around the commas makes the wrapping different, because
textwrap.wrapis sensitive to ANSI escape codes (those change the string length).There are 2 ways to fix this:
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
text_lentoTextWrapper#152702