Skip to content

Commit ef5d142

Browse files
gh-133653: Fix argparse.ArgumentParser with the formatter_class argument
* Fix TypeError when formatter_class is a custom subclass of HelpFormatter. * Fix TypeError when formatter_class is not a subclass of HelpFormatter and non-standard prefix_char is used. * Fix support of colorizing when formatter_class is not a subclass of HelpFormatter.
1 parent 30b1d8f commit ef5d142

File tree

3 files changed

+148
-22
lines changed

3 files changed

+148
-22
lines changed

Lib/argparse.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,7 @@ def __init__(
176176
width = shutil.get_terminal_size().columns
177177
width -= 2
178178

179-
from _colorize import can_colorize, decolor, get_theme
180-
181-
if color and can_colorize():
182-
self._theme = get_theme(force_color=True).argparse
183-
self._decolor = decolor
184-
else:
185-
self._theme = get_theme(force_no_color=True).argparse
186-
self._decolor = lambda text: text
187-
179+
self._set_color(color)
188180
self._prefix_chars = prefix_chars
189181
self._prog = prog
190182
self._indent_increment = indent_increment
@@ -202,6 +194,16 @@ def __init__(
202194
self._whitespace_matcher = _re.compile(r'\s+', _re.ASCII)
203195
self._long_break_matcher = _re.compile(r'\n\n\n+')
204196

197+
def _set_color(self, color):
198+
from _colorize import can_colorize, decolor, get_theme
199+
200+
if color and can_colorize():
201+
self._theme = get_theme(force_color=True).argparse
202+
self._decolor = decolor
203+
else:
204+
self._theme = get_theme(force_no_color=True).argparse
205+
self._decolor = lambda text: text
206+
205207
# ===============================
206208
# Section and indentation methods
207209
# ===============================
@@ -2723,16 +2725,10 @@ def format_help(self):
27232725
return formatter.format_help()
27242726

27252727
def _get_formatter(self):
2726-
if isinstance(self.formatter_class, type) and issubclass(
2727-
self.formatter_class, HelpFormatter
2728-
):
2729-
return self.formatter_class(
2730-
prog=self.prog,
2731-
prefix_chars=self.prefix_chars,
2732-
color=self.color,
2733-
)
2734-
else:
2735-
return self.formatter_class(prog=self.prog)
2728+
formatter = self.formatter_class(prog=self.prog)
2729+
formatter._prefix_chars = self.prefix_chars
2730+
formatter._set_color(self.color)
2731+
return formatter
27362732

27372733
# =====================
27382734
# Help-printing methods

Lib/test/test_argparse.py

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5469,11 +5469,60 @@ def custom_type(string):
54695469
version = ''
54705470

54715471

5472-
class TestHelpUsageLongSubparserCommand(TestCase):
5473-
"""Test that subparser commands are formatted correctly in help"""
5472+
class TestHelpCustomHelpFormatter(TestCase):
54745473
maxDiff = None
54755474

5476-
def test_parent_help(self):
5475+
def test_custom_formatter_function(self):
5476+
def custom_formatter(prog):
5477+
return argparse.RawTextHelpFormatter(prog, indent_increment=5)
5478+
5479+
parser = argparse.ArgumentParser(
5480+
prog='PROG',
5481+
prefix_chars='-+',
5482+
formatter_class=custom_formatter
5483+
)
5484+
parser.add_argument('+f', '++foo', help="foo help")
5485+
parser.add_argument('spam', help="spam help")
5486+
5487+
parser_help = parser.format_help()
5488+
self.assertEqual(parser_help, textwrap.dedent('''\
5489+
usage: PROG [-h] [+f FOO] spam
5490+
5491+
positional arguments:
5492+
spam spam help
5493+
5494+
options:
5495+
-h, --help show this help message and exit
5496+
+f, ++foo FOO foo help
5497+
'''))
5498+
5499+
def test_custom_formatter_class(self):
5500+
class CustomFormatter(argparse.RawTextHelpFormatter):
5501+
def __init__(self, prog):
5502+
super().__init__(prog, indent_increment=5)
5503+
5504+
parser = argparse.ArgumentParser(
5505+
prog='PROG',
5506+
prefix_chars='-+',
5507+
formatter_class=CustomFormatter
5508+
)
5509+
parser.add_argument('+f', '++foo', help="foo help")
5510+
parser.add_argument('spam', help="spam help")
5511+
5512+
parser_help = parser.format_help()
5513+
self.assertEqual(parser_help, textwrap.dedent('''\
5514+
usage: PROG [-h] [+f FOO] spam
5515+
5516+
positional arguments:
5517+
spam spam help
5518+
5519+
options:
5520+
-h, --help show this help message and exit
5521+
+f, ++foo FOO foo help
5522+
'''))
5523+
5524+
def test_usage_long_subparser_command(self):
5525+
"""Test that subparser commands are formatted correctly in help"""
54775526
def custom_formatter(prog):
54785527
return argparse.RawTextHelpFormatter(prog, max_help_position=50)
54795528

@@ -7053,6 +7102,7 @@ def test_translations(self):
70537102

70547103

70557104
class TestColorized(TestCase):
7105+
maxDiff = None
70567106

70577107
def setUp(self):
70587108
super().setUp()
@@ -7211,6 +7261,79 @@ def test_argparse_color_usage(self):
72117261
),
72127262
)
72137263

7264+
def test_custom_formatter_function(self):
7265+
def custom_formatter(prog):
7266+
return argparse.RawTextHelpFormatter(prog, indent_increment=5)
7267+
7268+
parser = argparse.ArgumentParser(
7269+
prog="PROG",
7270+
prefix_chars="-+",
7271+
formatter_class=custom_formatter,
7272+
color=True,
7273+
)
7274+
parser.add_argument('+f', '++foo', help="foo help")
7275+
parser.add_argument('spam', help="spam help")
7276+
7277+
prog = self.theme.prog
7278+
heading = self.theme.heading
7279+
short = self.theme.summary_short_option
7280+
label = self.theme.summary_label
7281+
pos = self.theme.summary_action
7282+
long_b = self.theme.long_option
7283+
short_b = self.theme.short_option
7284+
label_b = self.theme.label
7285+
pos_b = self.theme.action
7286+
reset = self.theme.reset
7287+
7288+
parser_help = parser.format_help()
7289+
self.assertEqual(parser_help, textwrap.dedent(f'''\
7290+
{heading}usage: {reset}{prog}PROG{reset} [{short}-h{reset}] [{short}+f {label}FOO{reset}] {pos}spam{reset}
7291+
7292+
{heading}positional arguments:{reset}
7293+
{pos_b}spam{reset} spam help
7294+
7295+
{heading}options:{reset}
7296+
{short_b}-h{reset}, {long_b}--help{reset} show this help message and exit
7297+
{short_b}+f{reset}, {long_b}++foo{reset} {label_b}FOO{reset} foo help
7298+
'''))
7299+
7300+
def test_custom_formatter_class(self):
7301+
class CustomFormatter(argparse.RawTextHelpFormatter):
7302+
def __init__(self, prog):
7303+
super().__init__(prog, indent_increment=5)
7304+
7305+
parser = argparse.ArgumentParser(
7306+
prog="PROG",
7307+
prefix_chars="-+",
7308+
formatter_class=CustomFormatter,
7309+
color=True,
7310+
)
7311+
parser.add_argument('+f', '++foo', help="foo help")
7312+
parser.add_argument('spam', help="spam help")
7313+
7314+
prog = self.theme.prog
7315+
heading = self.theme.heading
7316+
short = self.theme.summary_short_option
7317+
label = self.theme.summary_label
7318+
pos = self.theme.summary_action
7319+
long_b = self.theme.long_option
7320+
short_b = self.theme.short_option
7321+
label_b = self.theme.label
7322+
pos_b = self.theme.action
7323+
reset = self.theme.reset
7324+
7325+
parser_help = parser.format_help()
7326+
self.assertEqual(parser_help, textwrap.dedent(f'''\
7327+
{heading}usage: {reset}{prog}PROG{reset} [{short}-h{reset}] [{short}+f {label}FOO{reset}] {pos}spam{reset}
7328+
7329+
{heading}positional arguments:{reset}
7330+
{pos_b}spam{reset} spam help
7331+
7332+
{heading}options:{reset}
7333+
{short_b}-h{reset}, {long_b}--help{reset} show this help message and exit
7334+
{short_b}+f{reset}, {long_b}++foo{reset} {label_b}FOO{reset} foo help
7335+
'''))
7336+
72147337

72157338
def tearDownModule():
72167339
# Remove global references to avoid looking like we have refleaks.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Fix :class:`argparse.ArgumentParser` with the *formatter_class* argument.
2+
Fix TypeError when *formatter_class* is a custom subclass of
3+
:class:`!HelpFormatter`.
4+
Fix TypeError when *formatter_class* is not a subclass of
5+
:class:`!HelpFormatter` and non-standard *prefix_char* is used.
6+
Fix support of colorizing when *formatter_class* is not a subclass of
7+
:class:`!HelpFormatter`.

0 commit comments

Comments
 (0)