Skip to content

Commit 60e5004

Browse files
RaghuRaghu
authored andcommitted
gh-148468: Accept string-like help proxies in argparse
1 parent 9df2b6c commit 60e5004

3 files changed

Lines changed: 81 additions & 3 deletions

File tree

Lib/argparse.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ def _get_actions_usage_parts(self, actions, groups):
548548
return parts, pos_start
549549

550550
def _format_text(self, text):
551+
text = str(text)
551552
if '%(prog)' in text:
552553
text = text % dict(prog=self._prog)
553554
text_width = max(self._width - self._current_indent, 11)
@@ -564,6 +565,7 @@ def _apply_text_markup(self, text):
564565
565566
When colors are disabled, backticks are preserved as-is.
566567
"""
568+
text = str(text)
567569
t = self._theme
568570
if not t.reset:
569571
return text
@@ -610,9 +612,9 @@ def _format_action(self, action):
610612
parts = [action_header]
611613

612614
# if there was help for the action, add lines of help text
613-
if action.help and action.help.strip():
615+
if action.help:
614616
help_text = self._expand_help(action)
615-
if help_text:
617+
if help_text.strip():
616618
help_lines = self._split_lines(help_text, help_width)
617619
parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
618620
for line in help_lines[1:]:
@@ -711,7 +713,7 @@ def _format_args(self, action, default_metavar):
711713
return result
712714

713715
def _expand_help(self, action):
714-
help_string = self._get_help_string(action)
716+
help_string = str(self._get_help_string(action))
715717
if '%' not in help_string:
716718
return self._apply_text_markup(help_string)
717719
params = dict(vars(action), prog=self._prog)

Lib/test/test_argparse.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7398,6 +7398,19 @@ def setUp(self):
73987398
lambda *args, **kwargs: True))
73997399
self.theme = _colorize.get_theme(force_color=True).argparse
74007400

7401+
class _LazyStr:
7402+
def __init__(self, message):
7403+
self._message = message
7404+
7405+
def __str__(self):
7406+
return self._message
7407+
7408+
def __contains__(self, item):
7409+
return item in self._message
7410+
7411+
def __mod__(self, other):
7412+
return self._message % other
7413+
74017414
def test_argparse_color(self):
74027415
# Arrange: create a parser with a bit of everything
74037416
parser = argparse.ArgumentParser(
@@ -7736,6 +7749,38 @@ def test_backtick_markup_in_description(self):
77367749
)
77377750
self.assertNotIn("`", help_text)
77387751

7752+
def test_backtick_markup_in_description_accepts_string_like_proxy(self):
7753+
parser = argparse.ArgumentParser(
7754+
prog='PROG',
7755+
color=True,
7756+
description=self._LazyStr(
7757+
'Run `python myapp` or ``python -m myapp`` to start.'
7758+
),
7759+
)
7760+
7761+
prog_extra = self.theme.prog_extra
7762+
reset = self.theme.reset
7763+
7764+
help_text = parser.format_help()
7765+
self.assertIn(
7766+
f'Run {prog_extra}python myapp{reset} or '
7767+
f'{prog_extra}python -m myapp{reset} to start.',
7768+
help_text,
7769+
)
7770+
7771+
def test_backtick_markup_in_epilog_accepts_string_like_proxy(self):
7772+
parser = argparse.ArgumentParser(
7773+
prog='myapp',
7774+
color=True,
7775+
epilog=self._LazyStr('Run `%(prog)s --help` for more info.'),
7776+
)
7777+
7778+
prog_extra = self.theme.prog_extra
7779+
reset = self.theme.reset
7780+
7781+
help_text = parser.format_help()
7782+
self.assertIn(f'{prog_extra}myapp --help{reset}', help_text)
7783+
77397784
def test_backtick_markup_multiple(self):
77407785
parser = argparse.ArgumentParser(
77417786
prog='PROG',
@@ -7837,6 +7882,34 @@ def test_backtick_markup_in_argument_help_color_disabled(self):
78377882
self.assertIn("set the `foo` value", help_text)
78387883
self.assertNotIn("\x1b[", help_text)
78397884

7885+
def test_backtick_markup_in_argument_help_accepts_string_like_proxy(self):
7886+
parser = argparse.ArgumentParser(prog="PROG", color=True)
7887+
parser.add_argument(
7888+
"--foo", help=self._LazyStr("set the `foo` value")
7889+
)
7890+
7891+
prog_extra = self.theme.prog_extra
7892+
reset = self.theme.reset
7893+
7894+
help_text = parser.format_help()
7895+
self.assertIn(f"set the {prog_extra}foo{reset} value", help_text)
7896+
7897+
def test_argument_help_interpolation_accepts_string_like_proxy(self):
7898+
parser = argparse.ArgumentParser(prog="PROG", color=True)
7899+
parser.add_argument(
7900+
"--foo",
7901+
default="bar",
7902+
help=self._LazyStr("set `foo` (default: %(default)s)"),
7903+
)
7904+
7905+
prog_extra = self.theme.prog_extra
7906+
interp = self.theme.interpolated_value
7907+
reset = self.theme.reset
7908+
7909+
help_text = parser.format_help()
7910+
self.assertIn(f"set {prog_extra}foo{reset}", help_text)
7911+
self.assertIn(f"default: {interp}bar{reset}", help_text)
7912+
78407913
def test_help_with_format_specifiers(self):
78417914
# GH-142950: format specifiers like %x should work with color=True
78427915
parser = argparse.ArgumentParser(prog='PROG', color=True)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a regression in :mod:`argparse` so colorized help formatting once again
2+
accepts string-like proxy objects for descriptions, epilogs, and argument
3+
help text.

0 commit comments

Comments
 (0)