diff --git a/docs/tutorial/exceptions.md b/docs/tutorial/exceptions.md index 84dbac602c..654e0dfb57 100644 --- a/docs/tutorial/exceptions.md +++ b/docs/tutorial/exceptions.md @@ -202,6 +202,18 @@ $ python main.py + +## Set Output Width + +If you want to control the width of the yellow and red Rich exception borders, you can set the parameter `pretty_exceptions_width` to a specific integer +(by default it's the maximum console width if it can be determined, or otherwise 100): + +```Python hl_lines="3" +{!../docs_src/exceptions/tutorial005.py!} +``` + +This prevents artificial line breaks in cases where there is sufficient horizontal space on the console. + ## Disable Pretty Exceptions You can also entirely disable pretty exceptions with the parameter `pretty_exceptions_enable=False`: diff --git a/docs_src/exceptions/tutorial005.py b/docs_src/exceptions/tutorial005.py new file mode 100644 index 0000000000..759791c937 --- /dev/null +++ b/docs_src/exceptions/tutorial005.py @@ -0,0 +1,39 @@ +import typer + +app = typer.Typer(pretty_exceptions_width=120) + + +@app.command() +def main(name: str = "morty"): + deep_dict_or_json = { + "this_is_a_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "this_is_the_next_long_key": { + "and_once_again_a_very_long_key": { + "but_this_is_not_the_end": { + "end": True + } + } + } + } + } + } + } + } + } + } + } + } + } + print(name + deep_dict_or_json + 3) + + +if __name__ == "__main__": + app() diff --git a/tests/test_tutorial/test_exceptions/test_tutorial005.py b/tests/test_tutorial/test_exceptions/test_tutorial005.py new file mode 100644 index 0000000000..0e700e491d --- /dev/null +++ b/tests/test_tutorial/test_exceptions/test_tutorial005.py @@ -0,0 +1,37 @@ +import os +import subprocess +import sys +from pathlib import Path + +from typer.testing import CliRunner + +from docs_src.exceptions import tutorial005 as mod + +runner = CliRunner() + + +def test_traceback_rich_pretty_width(): + file_path = Path(mod.__file__) + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", str(file_path)], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TYPER_STANDARD_TRACEBACK": "", + "PYTHONIOENCODING": "utf-8", + }, + ) + + assert "print(name + deep_dict_or_json + 3)" in result.stderr + assert 'TypeError: can only concatenate str (not "dict") to str' in result.stderr + assert "name = 'morty'" in result.stderr + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/typer/main.py b/typer/main.py index 013939bf20..11737f7c9e 100644 --- a/typer/main.py +++ b/typer/main.py @@ -50,10 +50,13 @@ from . import rich_utils + DEFAULT_WIDTH = rich_utils.MAX_WIDTH + console_stderr = rich_utils._get_rich_console(stderr=True) except ImportError: # pragma: no cover rich = None # type: ignore + DEFAULT_WIDTH = 100 _original_except_hook = sys.excepthook _typer_developer_exception_attr_name = "__typer_developer_exception__" @@ -78,15 +81,13 @@ def except_hook( supress_internal_dir_names = [typer_path, click_path] exc = exc_value if rich: - from .rich_utils import MAX_WIDTH - rich_tb = Traceback.from_exception( type(exc), exc, exc.__traceback__, show_locals=exception_config.pretty_exceptions_show_locals, suppress=supress_internal_dir_names, - width=MAX_WIDTH, + width=exception_config.pretty_exceptions_width, ) console_stderr.print(rich_tb) return @@ -149,6 +150,7 @@ def __init__( pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, pretty_exceptions_short: bool = True, + pretty_exceptions_width: Union[int, None] = DEFAULT_WIDTH, ): self._add_completion = add_completion self.rich_markup_mode: MarkupMode = rich_markup_mode @@ -156,6 +158,7 @@ def __init__( self.pretty_exceptions_enable = pretty_exceptions_enable self.pretty_exceptions_show_locals = pretty_exceptions_show_locals self.pretty_exceptions_short = pretty_exceptions_short + self.pretty_exceptions_width = pretty_exceptions_width self.info = TyperInfo( name=name, cls=cls, @@ -333,6 +336,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any: pretty_exceptions_enable=self.pretty_exceptions_enable, pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, pretty_exceptions_short=self.pretty_exceptions_short, + pretty_exceptions_width=self.pretty_exceptions_width, ), ) raise e diff --git a/typer/models.py b/typer/models.py index 9bbe2a36d2..7d4f9e110e 100644 --- a/typer/models.py +++ b/typer/models.py @@ -513,7 +513,9 @@ def __init__( pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, pretty_exceptions_short: bool = True, + pretty_exceptions_width: Union[int, None] = 100, ) -> None: self.pretty_exceptions_enable = pretty_exceptions_enable self.pretty_exceptions_show_locals = pretty_exceptions_show_locals self.pretty_exceptions_short = pretty_exceptions_short + self.pretty_exceptions_width = pretty_exceptions_width