Skip to content

Commit 263e427

Browse files
g-arjonespre-commit-ci[bot]svlandeg
authored
🐛 Make sure rich.markup is imported when rendering help text (#1290)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: svlandeg <[email protected]>
1 parent f99defb commit 263e427

File tree

4 files changed

+53
-14
lines changed

4 files changed

+53
-14
lines changed

tests/test_rich_utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
13
import typer
24
import typer.completion
35
from typer.testing import CliRunner
@@ -79,3 +81,21 @@ def main(
7981
assert "Hello Rick" in result.stdout
8082
assert "First: option_1_default" in result.stdout
8183
assert "Second: Morty" in result.stdout
84+
85+
86+
def test_rich_markup_import_regression():
87+
# Remove rich.markup if it was imported by other tests
88+
if "rich" in sys.modules:
89+
rich_module = sys.modules["rich"]
90+
if hasattr(rich_module, "markup"):
91+
delattr(rich_module, "markup")
92+
93+
app = typer.Typer(rich_markup_mode=None)
94+
95+
@app.command()
96+
def main(bar: str):
97+
pass # pragma: no cover
98+
99+
result = runner.invoke(app, ["--help"])
100+
assert "Usage" in result.stdout
101+
assert "BAR" in result.stdout

typer/core.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,9 @@ def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]:
372372
extra_str = f"[{extra_str}]"
373373
if rich is not None:
374374
# This is needed for when we want to export to HTML
375-
extra_str = rich.markup.escape(extra_str).strip()
375+
from . import rich_utils
376+
377+
extra_str = rich_utils.escape_before_html_export(extra_str)
376378

377379
help = f"{help} {extra_str}" if help else f"{extra_str}"
378380
return name, help
@@ -583,7 +585,9 @@ def _write_opts(opts: Sequence[str]) -> str:
583585
extra_str = f"[{extra_str}]"
584586
if rich is not None:
585587
# This is needed for when we want to export to HTML
586-
extra_str = rich.markup.escape(extra_str).strip()
588+
from . import rich_utils
589+
590+
extra_str = rich_utils.escape_before_html_export(extra_str)
587591

588592
help = f"{help} {extra_str}" if help else f"{extra_str}"
589593

typer/main.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,19 @@ def except_hook(
7575
return
7676
typer_path = os.path.dirname(__file__)
7777
click_path = os.path.dirname(click.__file__)
78-
supress_internal_dir_names = [typer_path, click_path]
78+
internal_dir_names = [typer_path, click_path]
7979
exc = exc_value
8080
if rich:
81-
from rich.traceback import Traceback
82-
8381
from . import rich_utils
8482

85-
rich_tb = Traceback.from_exception(
86-
type(exc),
87-
exc,
88-
exc.__traceback__,
89-
show_locals=exception_config.pretty_exceptions_show_locals,
90-
suppress=supress_internal_dir_names,
91-
width=rich_utils.MAX_WIDTH,
92-
)
83+
rich_tb = rich_utils.get_traceback(exc, exception_config, internal_dir_names)
9384
console_stderr = rich_utils._get_rich_console(stderr=True)
9485
console_stderr.print(rich_tb)
9586
return
9687
tb_exc = traceback.TracebackException.from_exception(exc)
9788
stack: List[FrameSummary] = []
9889
for frame in tb_exc.stack:
99-
if any(frame.filename.startswith(path) for path in supress_internal_dir_names):
90+
if any(frame.filename.startswith(path) for path in internal_dir_names):
10091
if not exception_config.pretty_exceptions_short:
10192
# Hide the line for internal libraries, Typer and Click
10293
stack.append(

typer/rich_utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
from rich.emoji import Emoji
1717
from rich.highlighter import RegexHighlighter
1818
from rich.markdown import Markdown
19+
from rich.markup import escape
1920
from rich.padding import Padding
2021
from rich.panel import Panel
2122
from rich.table import Table
2223
from rich.text import Text
2324
from rich.theme import Theme
25+
from rich.traceback import Traceback
26+
from typer.models import DeveloperExceptionConfig
2427

2528
if sys.version_info >= (3, 9):
2629
from typing import Literal
@@ -727,6 +730,11 @@ def rich_abort_error() -> None:
727730
console.print(ABORTED_TEXT, style=STYLE_ABORTED)
728731

729732

733+
def escape_before_html_export(input_text: str) -> str:
734+
"""Ensure that the input string can be used for HTML export."""
735+
return escape(input_text).strip()
736+
737+
730738
def rich_to_html(input_text: str) -> str:
731739
"""Print the HTML version of a rich-formatted input string.
732740
@@ -744,3 +752,19 @@ def rich_render_text(text: str) -> str:
744752
"""Remove rich tags and render a pure text representation"""
745753
console = _get_rich_console()
746754
return "".join(segment.text for segment in console.render(text)).rstrip("\n")
755+
756+
757+
def get_traceback(
758+
exc: BaseException,
759+
exception_config: DeveloperExceptionConfig,
760+
internal_dir_names: List[str],
761+
) -> Traceback:
762+
rich_tb = Traceback.from_exception(
763+
type(exc),
764+
exc,
765+
exc.__traceback__,
766+
show_locals=exception_config.pretty_exceptions_show_locals,
767+
suppress=internal_dir_names,
768+
width=MAX_WIDTH,
769+
)
770+
return rich_tb

0 commit comments

Comments
 (0)