From e5bbeeac76029527247a876abbc4982ff17f9753 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 13 Oct 2025 16:56:36 +0100 Subject: [PATCH 1/4] Commit --- Lib/_colorize.py | 1 + Lib/test/test_traceback.py | 25 ++++++++++++++++--- Lib/traceback.py | 8 ++++-- ...-10-13-16-43-36.gh-issue-140049.VvmAzN.rst | 1 + 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-13-16-43-36.gh-issue-140049.VvmAzN.rst diff --git a/Lib/_colorize.py b/Lib/_colorize.py index d35486296f2684..fc375b4de0ad77 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -202,6 +202,7 @@ class Syntax(ThemeSection): class Traceback(ThemeSection): type: str = ANSIColors.BOLD_MAGENTA message: str = ANSIColors.MAGENTA + note: str = ANSIColors.MAGENTA filename: str = ANSIColors.MAGENTA line_no: str = ANSIColors.MAGENTA frame: str = ANSIColors.MAGENTA diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index bd3ecfd9a3863d..65392f2d2e471d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -37,7 +37,7 @@ test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals']) test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti']) -color_overrides = {"reset": "z", "filename": "fn", "error_highlight": "E"} +color_overrides = {"reset": "z", "filename": "fn", "error_highlight": "E", "note": "n"} colors = { color_overrides.get(k, k[0].lower()): v for k, v in _colorize.default_theme.traceback.items() @@ -5085,6 +5085,23 @@ def bar(): self.assertIn("return baz1(1,\n 2,3\n ,4)", lines) self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) + def test_colorized_exception_notes(self): + def foo(): + raise ValueError() + + try: + foo() + except Exception as e: + e.add_note("First note") + e.add_note("Second note") + exc = traceback.TracebackException.from_exception(e) + + lines = "".join(exc.format(colorize=True)) + magenta = colors["m"] + reset = colors["z"] + self.assertIn(magenta + "First note" + reset, lines) + self.assertIn(magenta + "Second note" + reset, lines) + def test_colorized_syntax_error(self): try: compile("a $ b", "", "exec") @@ -5093,7 +5110,7 @@ def test_colorized_syntax_error(self): e, capture_locals=True ) actual = "".join(exc.format(colorize=True)) - def expected(t, m, fn, l, f, E, e, z): + def expected(t, m, fn, l, f, E, e, z, n): return "".join( [ f' File {fn}""{z}, line {l}1{z}\n', @@ -5119,7 +5136,7 @@ def foo(): actual = tbstderr.getvalue().splitlines() lno_foo = foo.__code__.co_firstlineno - def expected(t, m, fn, l, f, E, e, z): + def expected(t, m, fn, l, f, E, e, z, n): return [ 'Traceback (most recent call last):', f' File {fn}"{__file__}"{z}, ' @@ -5152,7 +5169,7 @@ def foo(): lno_foo = foo.__code__.co_firstlineno actual = "".join(exc.format(colorize=True)).splitlines() - def expected(t, m, fn, l, f, E, e, z): + def expected(t, m, fn, l, f, E, e, z, n): return [ f" + Exception Group Traceback (most recent call last):", f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+9}{z}, in {f}test_colorized_traceback_from_exception_group{z}', diff --git a/Lib/traceback.py b/Lib/traceback.py index 692d44837936ee..234dc872b997c3 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1253,6 +1253,10 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs): well, recursively, with indentation relative to their nesting depth. """ colorize = kwargs.get("colorize", False) + if colorize: + theme = _colorize.get_theme(force_color=True).traceback + else: + theme = _colorize.get_theme(force_no_color=True).traceback indent = 3 * _depth * ' ' if not self._have_exc_type: @@ -1281,9 +1285,9 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs): ): for note in self.__notes__: note = _safe_string(note, 'note') - yield from [indent + l + '\n' for l in note.split('\n')] + yield from [indent + theme.note + l + theme.reset + '\n' for l in note.split('\n')] elif self.__notes__ is not None: - yield indent + "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr)) + yield indent + theme.note + "{}".format(_safe_string(self.__notes__, '__notes__', func=repr)) + theme.reset + "\n" if self.exceptions and show_group: for ex in self.exceptions: diff --git a/Misc/NEWS.d/next/Library/2025-10-13-16-43-36.gh-issue-140049.VvmAzN.rst b/Misc/NEWS.d/next/Library/2025-10-13-16-43-36.gh-issue-140049.VvmAzN.rst new file mode 100644 index 00000000000000..d9489fd0baab91 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-13-16-43-36.gh-issue-140049.VvmAzN.rst @@ -0,0 +1 @@ +:func:`traceback.format_exception_only` now colorizes exception notes. From 47f74a21441f7f9f231a52a7a52b1faa0892e00d Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 21 Mar 2026 18:07:58 +0000 Subject: [PATCH 2/4] Chnge color to CYAN, refactor coloring --- Lib/_colorize.py | 2 +- Lib/test/test_traceback.py | 6 +++--- Lib/traceback.py | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 3a473cec66115a..fd0ae9d6145961 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -327,7 +327,7 @@ class Syntax(ThemeSection): class Traceback(ThemeSection): type: str = ANSIColors.BOLD_MAGENTA message: str = ANSIColors.MAGENTA - note: str = ANSIColors.MAGENTA + note: str = ANSIColors.CYAN filename: str = ANSIColors.MAGENTA line_no: str = ANSIColors.MAGENTA frame: str = ANSIColors.MAGENTA diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 3339ebd8c3daa0..5dc11253e0d5c8 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -5318,10 +5318,10 @@ def foo(): exc = traceback.TracebackException.from_exception(e) lines = "".join(exc.format(colorize=True)) - magenta = colors["m"] + note = colors["n"] reset = colors["z"] - self.assertIn(magenta + "First note" + reset, lines) - self.assertIn(magenta + "Second note" + reset, lines) + self.assertIn(note + "First note" + reset, lines) + self.assertIn(note + "Second note" + reset, lines) def test_colorized_syntax_error(self): try: diff --git a/Lib/traceback.py b/Lib/traceback.py index 7f79183dd3565a..4887eeb0f559b4 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -993,6 +993,11 @@ def _display_width(line, offset=None): ) +def _format_note(note, indent, theme): + for l in note.split('\n'): + yield f"{indent}{theme.note}{l}{theme.reset}\n" + + class _ExceptionPrintContext: def __init__(self): @@ -1323,9 +1328,10 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs): ): for note in self.__notes__: note = _safe_string(note, 'note') - yield from [indent + theme.note + l + theme.reset + '\n' for l in note.split('\n')] + yield from _format_note(note, indent, theme) elif self.__notes__ is not None: - yield indent + theme.note + "{}".format(_safe_string(self.__notes__, '__notes__', func=repr)) + theme.reset + "\n" + note = _safe_string(self.__notes__, '__notes__', func=repr) + yield from _format_note(note, indent, theme) if self.exceptions and show_group: for ex in self.exceptions: From a7e3fef635d19a211dcc1886a78676f586086100 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 21 Mar 2026 18:09:06 +0000 Subject: [PATCH 3/4] Oops one too many --- Lib/traceback.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 4887eeb0f559b4..7a3ebd3fad49d1 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -998,7 +998,6 @@ def _format_note(note, indent, theme): yield f"{indent}{theme.note}{l}{theme.reset}\n" - class _ExceptionPrintContext: def __init__(self): self.seen = set() From a44e6f170dd0c9455bc4386d50e875232d2d7d09 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:26:07 +0200 Subject: [PATCH 4/4] Formatting --- Lib/traceback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/traceback.py b/Lib/traceback.py index 7a3ebd3fad49d1..1f9f151ebf5d39 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -994,7 +994,7 @@ def _display_width(line, offset=None): def _format_note(note, indent, theme): - for l in note.split('\n'): + for l in note.split("\n"): yield f"{indent}{theme.note}{l}{theme.reset}\n"