From 77ee83802f2f21182f3e71d7bc379adadd90d4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:26:19 +0100 Subject: [PATCH 1/8] Option to suppress modules in Rich tracebacks --- docs/tutorial/exceptions.md | 21 +++++++++++++++++++++ docs_src/exceptions/tutorial005.py | 18 ++++++++++++++++++ typer/main.py | 7 +++++-- typer/models.py | 4 ++++ typer/rich_utils.py | 3 ++- 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 docs_src/exceptions/tutorial005.py diff --git a/docs/tutorial/exceptions.md b/docs/tutorial/exceptions.md index 43ca2d0f6d..7b26cac436 100644 --- a/docs/tutorial/exceptions.md +++ b/docs/tutorial/exceptions.md @@ -196,6 +196,27 @@ $ python main.py +## Disable Tracebacks From Certain Modules + +If you are developing with Python frameworks other than **Typer** and **Click**, +you might get very verbose tracebacks, which could make it difficult to find the +line in your own code that triggered the exception. + +With pretty exceptions, you can use the parameter `pretty_exceptions_suppress`, +which takes a list of Python modules, or `str` paths, to indicate which modules +should have their traceback frames suppressed by the **Rich** traceback +formatter. Only filename and line number will be shown for these modules, but no +code or variables. ⚡ + +For example, if you are developing a GitLab utility using the `python-gitlab` +package, you might notice that tracebacks are very long and filled with internal +calls inside `gitlab` module that you probably do not care about. In this case, +you can suppress the traceback frames inside the `gitlab` module: + +{* docs_src/exceptions/tutorial005.py hl[3] *} + +And now can see clearly which of your calls to `gitlab` caused the exception. 💡 + ## 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..b3447b53ec --- /dev/null +++ b/docs_src/exceptions/tutorial005.py @@ -0,0 +1,18 @@ +import gitlab +import typer + +app = typer.Typer(pretty_exceptions_suppress=[gitlab]) + + +@app.command() +def main(): + gitlab_client = gitlab.Gitlab() + + # This will raise an exception if not authenticated: + # GitlabAuthenticationError: 401: 401 Unauthorized + # But the exception traceback will not show any lines from the gitlab module! + print(gitlab_client.pagesdomains.list()) + + +if __name__ == "__main__": + app() diff --git a/typer/main.py b/typer/main.py index 71a25e6c4b..5a9b848289 100644 --- a/typer/main.py +++ b/typer/main.py @@ -10,8 +10,8 @@ from functools import update_wrapper from pathlib import Path from traceback import FrameSummary, StackSummary -from types import TracebackType -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +from types import ModuleType, TracebackType +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID import click @@ -139,6 +139,7 @@ def __init__( pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, pretty_exceptions_short: bool = True, + pretty_exceptions_suppress: Iterable[Union[str, ModuleType]] = (), ): self._add_completion = add_completion self.rich_markup_mode: MarkupMode = rich_markup_mode @@ -147,6 +148,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_suppress = pretty_exceptions_suppress self.info = TyperInfo( name=name, cls=cls, @@ -322,6 +324,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_suppress=self.pretty_exceptions_suppress, ), ) raise e diff --git a/typer/models.py b/typer/models.py index e0bddb965b..bd0438b908 100644 --- a/typer/models.py +++ b/typer/models.py @@ -1,10 +1,12 @@ import inspect import io +from types import ModuleType from typing import ( TYPE_CHECKING, Any, Callable, Dict, + Iterable, List, Optional, Sequence, @@ -527,10 +529,12 @@ def __init__( pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, pretty_exceptions_short: bool = True, + pretty_exceptions_suppress: Iterable[Union[str, ModuleType]] = (), ) -> 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_suppress = pretty_exceptions_suppress class TyperPath(click.Path): diff --git a/typer/rich_utils.py b/typer/rich_utils.py index d4c3676aea..35dd7b0baa 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -759,12 +759,13 @@ def get_traceback( exception_config: DeveloperExceptionConfig, internal_dir_names: List[str], ) -> Traceback: + suppress = (*internal_dir_names, *exception_config.pretty_exceptions_suppress) rich_tb = Traceback.from_exception( type(exc), exc, exc.__traceback__, show_locals=exception_config.pretty_exceptions_show_locals, - suppress=internal_dir_names, + suppress=suppress, width=MAX_WIDTH, ) return rich_tb From fffa6ee8fe00051a1ba83a5b1ebeb25a803825a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:33:22 +0100 Subject: [PATCH 2/8] Ruff format --- typer/main.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/typer/main.py b/typer/main.py index 5a9b848289..545169fbdd 100644 --- a/typer/main.py +++ b/typer/main.py @@ -11,7 +11,18 @@ from pathlib import Path from traceback import FrameSummary, StackSummary from types import ModuleType, TracebackType -from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Sequence, + Tuple, + Type, + Union, +) from uuid import UUID import click From 41ce825453ccea46646a8ac10361466fc6b5a42d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:08:50 +0000 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/main.py | 4 ++-- typer/models.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/typer/main.py b/typer/main.py index 56daf24a1c..13e777a2cc 100644 --- a/typer/main.py +++ b/typer/main.py @@ -5,14 +5,14 @@ import subprocess import sys import traceback -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from datetime import datetime from enum import Enum from functools import update_wrapper from pathlib import Path from traceback import FrameSummary, StackSummary from types import ModuleType, TracebackType -from typing import Any, Callable, Iterable, Optional, Union +from typing import Any, Callable, Optional, Union from uuid import UUID import click diff --git a/typer/models.py b/typer/models.py index db7ae19b4a..29d984f206 100644 --- a/typer/models.py +++ b/typer/models.py @@ -1,12 +1,11 @@ import inspect import io +from collections.abc import Iterable, Sequence from types import ModuleType -from collections.abc import Sequence from typing import ( TYPE_CHECKING, Any, Callable, - Iterable, Optional, TypeVar, Union, From 8e2cd6230c5a0e928824edafbc2fee9692f6b7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:17:56 +0100 Subject: [PATCH 4/8] Fix spelling and grammar in docs --- docs/tutorial/exceptions.md | 6 +++--- docs_src/exceptions/tutorial005.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/exceptions.md b/docs/tutorial/exceptions.md index e3307ca5f6..748fd99470 100644 --- a/docs/tutorial/exceptions.md +++ b/docs/tutorial/exceptions.md @@ -210,12 +210,12 @@ code or variables. ⚡ For example, if you are developing a GitLab utility using the `python-gitlab` package, you might notice that tracebacks are very long and filled with internal -calls inside `gitlab` module that you probably do not care about. In this case, +calls inside the `gitlab` module that you probably do not care about. In this case, you can suppress the traceback frames inside the `gitlab` module: -{* docs_src/exceptions/tutorial005.py hl[3] *} +{* docs_src/exceptions/tutorial005.py hl[4] *} -And now can see clearly which of your calls to `gitlab` caused the exception. 💡 +And now you can see clearly which of your calls to `gitlab` caused the exception. 💡 ## Disable Pretty Exceptions diff --git a/docs_src/exceptions/tutorial005.py b/docs_src/exceptions/tutorial005.py index b3447b53ec..b710caf30d 100644 --- a/docs_src/exceptions/tutorial005.py +++ b/docs_src/exceptions/tutorial005.py @@ -10,7 +10,7 @@ def main(): # This will raise an exception if not authenticated: # GitlabAuthenticationError: 401: 401 Unauthorized - # But the exception traceback will not show any lines from the gitlab module! + # But the traceback will not show any lines from the gitlab module! print(gitlab_client.pagesdomains.list()) From e2d539d51b03e7066649f667dd6720eaca9b76ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Mellstr=C3=B6m?= <11281108+harkabeeparolus@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:31:21 +0100 Subject: [PATCH 5/8] Update to new docs filenames --- docs/tutorial/exceptions.md | 2 +- docs_src/exceptions/{tutorial005.py => tutorial005_py39.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs_src/exceptions/{tutorial005.py => tutorial005_py39.py} (100%) diff --git a/docs/tutorial/exceptions.md b/docs/tutorial/exceptions.md index 748fd99470..8808a4b33d 100644 --- a/docs/tutorial/exceptions.md +++ b/docs/tutorial/exceptions.md @@ -213,7 +213,7 @@ package, you might notice that tracebacks are very long and filled with internal calls inside the `gitlab` module that you probably do not care about. In this case, you can suppress the traceback frames inside the `gitlab` module: -{* docs_src/exceptions/tutorial005.py hl[4] *} +{* docs_src/exceptions/tutorial005_py39.py hl[4] *} And now you can see clearly which of your calls to `gitlab` caused the exception. 💡 diff --git a/docs_src/exceptions/tutorial005.py b/docs_src/exceptions/tutorial005_py39.py similarity index 100% rename from docs_src/exceptions/tutorial005.py rename to docs_src/exceptions/tutorial005_py39.py From a3260ec234896327194ed392e4b149603ea0b28d Mon Sep 17 00:00:00 2001 From: Fredrik Mellstrom <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:46:43 +0100 Subject: [PATCH 6/8] Add test for pretty_exceptions_suppress tutorial --- .../test_exceptions/test_tutorial005.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_tutorial/test_exceptions/test_tutorial005.py 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..2038d3f56d --- /dev/null +++ b/tests/test_tutorial/test_exceptions/test_tutorial005.py @@ -0,0 +1,25 @@ +import sys +from types import ModuleType +from unittest.mock import MagicMock + +from typer.testing import CliRunner + +# Mock the gitlab module before importing the tutorial +_gitlab = ModuleType("gitlab") +_gitlab.Gitlab = MagicMock # type: ignore[attr-defined] +_gitlab.__file__ = "gitlab/__init__.py" # type: ignore[attr-defined] +sys.modules.setdefault("gitlab", _gitlab) + +from docs_src.exceptions import tutorial005_py39 as mod # noqa: E402 + +runner = CliRunner() + + +def test_pretty_exceptions_suppress(): + result = runner.invoke(mod.app) + assert result.exit_code == 0 + + +def test_script(): + result = runner.invoke(mod.app, ["--help"]) + assert "Usage" in result.stdout From 4f0f19589eb0400df082340806e869c04af8a54e Mon Sep 17 00:00:00 2001 From: Fredrik Mellstrom <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 18 Feb 2026 07:33:23 +0100 Subject: [PATCH 7/8] Rename tutorial to _py310 --- .../{tutorial005_py39.py => tutorial005_py310.py} | 0 tests/test_tutorial/test_exceptions/test_tutorial005.py | 9 ++++++++- 2 files changed, 8 insertions(+), 1 deletion(-) rename docs_src/exceptions/{tutorial005_py39.py => tutorial005_py310.py} (100%) diff --git a/docs_src/exceptions/tutorial005_py39.py b/docs_src/exceptions/tutorial005_py310.py similarity index 100% rename from docs_src/exceptions/tutorial005_py39.py rename to docs_src/exceptions/tutorial005_py310.py diff --git a/tests/test_tutorial/test_exceptions/test_tutorial005.py b/tests/test_tutorial/test_exceptions/test_tutorial005.py index 2038d3f56d..bf33d5c80a 100644 --- a/tests/test_tutorial/test_exceptions/test_tutorial005.py +++ b/tests/test_tutorial/test_exceptions/test_tutorial005.py @@ -10,10 +10,17 @@ _gitlab.__file__ = "gitlab/__init__.py" # type: ignore[attr-defined] sys.modules.setdefault("gitlab", _gitlab) -from docs_src.exceptions import tutorial005_py39 as mod # noqa: E402 +from docs_src.exceptions import tutorial005_py310 as mod # noqa: E402 runner = CliRunner() +# There's no way to test a third-party package from PyPI. Also, the actual +# feature we use is part of Rich. We just pass the flag along. So here we test +# that the tutorial code runs without errors. + +# Perhaps we could find some standard library module that throws a long +# traceback and test that instead, but for now this is probably good enough. + def test_pretty_exceptions_suppress(): result = runner.invoke(mod.app) From a2f39dfca6392c8580c6a09b5024fddff56c479a Mon Sep 17 00:00:00 2001 From: Fredrik Mellstrom <11281108+harkabeeparolus@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:04:45 +0100 Subject: [PATCH 8/8] Update docs include for renamed tutorial src file --- docs/tutorial/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/exceptions.md b/docs/tutorial/exceptions.md index dfcd3a1b95..92b5132e9c 100644 --- a/docs/tutorial/exceptions.md +++ b/docs/tutorial/exceptions.md @@ -209,7 +209,7 @@ package, you might notice that tracebacks are very long and filled with internal calls inside the `gitlab` module that you probably do not care about. In this case, you can suppress the traceback frames inside the `gitlab` module: -{* docs_src/exceptions/tutorial005_py39.py hl[4] *} +{* docs_src/exceptions/tutorial005_py310.py hl[4] *} And now you can see clearly which of your calls to `gitlab` caused the exception. 💡