Skip to content

Commit f4d8e10

Browse files
iritkatrielerlend-aaslandambv
authored
gh-105292: Add option to make traceback.TracebackException.format_exception_only recurse into exception groups (#105294)
Co-authored-by: Erlend E. Aasland <[email protected]> Co-authored-by: Łukasz Langa <[email protected]>
1 parent 92022d8 commit f4d8e10

File tree

5 files changed

+48
-13
lines changed

5 files changed

+48
-13
lines changed

Doc/library/traceback.rst

+13-7
Original file line numberDiff line numberDiff line change
@@ -333,19 +333,24 @@ capture data for later printing in a lightweight fashion.
333333
The message indicating which exception occurred is always the last
334334
string in the output.
335335

336-
.. method:: format_exception_only()
336+
.. method:: format_exception_only(*, show_group=False)
337337

338338
Format the exception part of the traceback.
339339

340340
The return value is a generator of strings, each ending in a newline.
341341

342-
Normally, the generator emits a single string; however, for
343-
:exc:`SyntaxError` exceptions, it emits several lines that (when
344-
printed) display detailed information about where the syntax
345-
error occurred.
342+
When *show_group* is ``False``, the generator normally emits a single
343+
string; however, for :exc:`SyntaxError` exceptions, it emits several
344+
lines that (when printed) display detailed information about where
345+
the syntax error occurred. The message indicating which exception
346+
occurred is always the last string in the output.
346347

347-
The message indicating which exception occurred is always the last
348-
string in the output.
348+
When *show_group* is ``True``, and the exception is an instance of
349+
:exc:`BaseExceptionGroup`, the nested exceptions are included as
350+
well, recursively, with indentation relative to their nesting depth.
351+
352+
.. versionchanged:: 3.13
353+
Added the *show_group* parameter.
349354

350355
.. versionchanged:: 3.10
351356
Added the *compact* parameter.
@@ -354,6 +359,7 @@ capture data for later printing in a lightweight fashion.
354359
Added the *max_group_width* and *max_group_depth* parameters.
355360

356361

362+
357363
:class:`StackSummary` Objects
358364
-----------------------------
359365

Doc/whatsnew/3.13.rst

+7
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ pathlib
113113
:meth:`~pathlib.Path.rglob`.
114114
(Contributed by Barney Gale in :gh:`77609`.)
115115

116+
traceback
117+
---------
118+
119+
* Add *show_group* paramter to :func:`traceback.TracebackException.format_exception_only`
120+
to format the nested exceptions of a :exc:`BaseExceptionGroup` instance, recursively.
121+
(Contributed by Irit Katriel in :gh:`105292`.)
122+
116123
Optimizations
117124
=============
118125

Lib/test/test_traceback.py

+14
Original file line numberDiff line numberDiff line change
@@ -2792,6 +2792,20 @@ def test_exception_group_format_exception_only(self):
27922792

27932793
self.assertEqual(formatted, expected)
27942794

2795+
def test_exception_group_format_exception_onlyi_recursive(self):
2796+
teg = traceback.TracebackException.from_exception(self.eg)
2797+
formatted = ''.join(teg.format_exception_only(show_group=True)).split('\n')
2798+
expected = [
2799+
'ExceptionGroup: eg2 (2 sub-exceptions)',
2800+
' ExceptionGroup: eg1 (2 sub-exceptions)',
2801+
' ZeroDivisionError: division by zero',
2802+
' ValueError: 42',
2803+
' ValueError: 24',
2804+
''
2805+
]
2806+
2807+
self.assertEqual(formatted, expected)
2808+
27952809
def test_exception_group_format(self):
27962810
teg = traceback.TracebackException.from_exception(self.eg)
27972811

Lib/traceback.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ def __eq__(self, other):
826826
def __str__(self):
827827
return self._str
828828

829-
def format_exception_only(self):
829+
def format_exception_only(self, *, show_group=False, _depth=0):
830830
"""Format the exception part of the traceback.
831831
832832
The return value is a generator of strings, each ending in a newline.
@@ -839,8 +839,10 @@ def format_exception_only(self):
839839
The message indicating which exception occurred is always the last
840840
string in the output.
841841
"""
842+
843+
indent = 3 * _depth * ' '
842844
if self.exc_type is None:
843-
yield _format_final_exc_line(None, self._str)
845+
yield indent + _format_final_exc_line(None, self._str)
844846
return
845847

846848
stype = self.exc_type.__qualname__
@@ -851,19 +853,23 @@ def format_exception_only(self):
851853
stype = smod + '.' + stype
852854

853855
if not issubclass(self.exc_type, SyntaxError):
854-
yield _format_final_exc_line(stype, self._str)
856+
yield indent + _format_final_exc_line(stype, self._str)
855857
else:
856-
yield from self._format_syntax_error(stype)
858+
yield from [indent + l for l in self._format_syntax_error(stype)]
857859

858860
if (
859861
isinstance(self.__notes__, collections.abc.Sequence)
860862
and not isinstance(self.__notes__, (str, bytes))
861863
):
862864
for note in self.__notes__:
863865
note = _safe_string(note, 'note')
864-
yield from [l + '\n' for l in note.split('\n')]
866+
yield from [indent + l + '\n' for l in note.split('\n')]
865867
elif self.__notes__ is not None:
866-
yield "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
868+
yield indent + "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
869+
870+
if self.exceptions and show_group:
871+
for ex in self.exceptions:
872+
yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1)
867873

868874
def _format_syntax_error(self, stype):
869875
"""Format SyntaxError exceptions (internal helper)."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add option to :func:`traceback.format_exception_only` to recurse into the
2+
nested exception of a :exc:`BaseExceptionGroup`.

0 commit comments

Comments
 (0)