mirror of https://github.com/python/cpython
gh-105292: Add option to make traceback.TracebackException.format_exception_only recurse into exception groups (#105294)
Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
92022d8416
commit
f4d8e10d0d
|
@ -333,19 +333,24 @@ capture data for later printing in a lightweight fashion.
|
||||||
The message indicating which exception occurred is always the last
|
The message indicating which exception occurred is always the last
|
||||||
string in the output.
|
string in the output.
|
||||||
|
|
||||||
.. method:: format_exception_only()
|
.. method:: format_exception_only(*, show_group=False)
|
||||||
|
|
||||||
Format the exception part of the traceback.
|
Format the exception part of the traceback.
|
||||||
|
|
||||||
The return value is a generator of strings, each ending in a newline.
|
The return value is a generator of strings, each ending in a newline.
|
||||||
|
|
||||||
Normally, the generator emits a single string; however, for
|
When *show_group* is ``False``, the generator normally emits a single
|
||||||
:exc:`SyntaxError` exceptions, it emits several lines that (when
|
string; however, for :exc:`SyntaxError` exceptions, it emits several
|
||||||
printed) display detailed information about where the syntax
|
lines that (when printed) display detailed information about where
|
||||||
error occurred.
|
the syntax error occurred. The message indicating which exception
|
||||||
|
occurred is always the last string in the output.
|
||||||
|
|
||||||
The message indicating which exception occurred is always the last
|
When *show_group* is ``True``, and the exception is an instance of
|
||||||
string in the output.
|
:exc:`BaseExceptionGroup`, the nested exceptions are included as
|
||||||
|
well, recursively, with indentation relative to their nesting depth.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Added the *show_group* parameter.
|
||||||
|
|
||||||
.. versionchanged:: 3.10
|
.. versionchanged:: 3.10
|
||||||
Added the *compact* parameter.
|
Added the *compact* parameter.
|
||||||
|
@ -354,6 +359,7 @@ capture data for later printing in a lightweight fashion.
|
||||||
Added the *max_group_width* and *max_group_depth* parameters.
|
Added the *max_group_width* and *max_group_depth* parameters.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
:class:`StackSummary` Objects
|
:class:`StackSummary` Objects
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,13 @@ pathlib
|
||||||
:meth:`~pathlib.Path.rglob`.
|
:meth:`~pathlib.Path.rglob`.
|
||||||
(Contributed by Barney Gale in :gh:`77609`.)
|
(Contributed by Barney Gale in :gh:`77609`.)
|
||||||
|
|
||||||
|
traceback
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Add *show_group* paramter to :func:`traceback.TracebackException.format_exception_only`
|
||||||
|
to format the nested exceptions of a :exc:`BaseExceptionGroup` instance, recursively.
|
||||||
|
(Contributed by Irit Katriel in :gh:`105292`.)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -2792,6 +2792,20 @@ class TestTracebackException_ExceptionGroups(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(formatted, expected)
|
self.assertEqual(formatted, expected)
|
||||||
|
|
||||||
|
def test_exception_group_format_exception_onlyi_recursive(self):
|
||||||
|
teg = traceback.TracebackException.from_exception(self.eg)
|
||||||
|
formatted = ''.join(teg.format_exception_only(show_group=True)).split('\n')
|
||||||
|
expected = [
|
||||||
|
'ExceptionGroup: eg2 (2 sub-exceptions)',
|
||||||
|
' ExceptionGroup: eg1 (2 sub-exceptions)',
|
||||||
|
' ZeroDivisionError: division by zero',
|
||||||
|
' ValueError: 42',
|
||||||
|
' ValueError: 24',
|
||||||
|
''
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(formatted, expected)
|
||||||
|
|
||||||
def test_exception_group_format(self):
|
def test_exception_group_format(self):
|
||||||
teg = traceback.TracebackException.from_exception(self.eg)
|
teg = traceback.TracebackException.from_exception(self.eg)
|
||||||
|
|
||||||
|
|
|
@ -826,7 +826,7 @@ class TracebackException:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self._str
|
return self._str
|
||||||
|
|
||||||
def format_exception_only(self):
|
def format_exception_only(self, *, show_group=False, _depth=0):
|
||||||
"""Format the exception part of the traceback.
|
"""Format the exception part of the traceback.
|
||||||
|
|
||||||
The return value is a generator of strings, each ending in a newline.
|
The return value is a generator of strings, each ending in a newline.
|
||||||
|
@ -839,8 +839,10 @@ class TracebackException:
|
||||||
The message indicating which exception occurred is always the last
|
The message indicating which exception occurred is always the last
|
||||||
string in the output.
|
string in the output.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
indent = 3 * _depth * ' '
|
||||||
if self.exc_type is None:
|
if self.exc_type is None:
|
||||||
yield _format_final_exc_line(None, self._str)
|
yield indent + _format_final_exc_line(None, self._str)
|
||||||
return
|
return
|
||||||
|
|
||||||
stype = self.exc_type.__qualname__
|
stype = self.exc_type.__qualname__
|
||||||
|
@ -851,9 +853,9 @@ class TracebackException:
|
||||||
stype = smod + '.' + stype
|
stype = smod + '.' + stype
|
||||||
|
|
||||||
if not issubclass(self.exc_type, SyntaxError):
|
if not issubclass(self.exc_type, SyntaxError):
|
||||||
yield _format_final_exc_line(stype, self._str)
|
yield indent + _format_final_exc_line(stype, self._str)
|
||||||
else:
|
else:
|
||||||
yield from self._format_syntax_error(stype)
|
yield from [indent + l for l in self._format_syntax_error(stype)]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isinstance(self.__notes__, collections.abc.Sequence)
|
isinstance(self.__notes__, collections.abc.Sequence)
|
||||||
|
@ -861,9 +863,13 @@ class TracebackException:
|
||||||
):
|
):
|
||||||
for note in self.__notes__:
|
for note in self.__notes__:
|
||||||
note = _safe_string(note, 'note')
|
note = _safe_string(note, 'note')
|
||||||
yield from [l + '\n' for l in note.split('\n')]
|
yield from [indent + l + '\n' for l in note.split('\n')]
|
||||||
elif self.__notes__ is not None:
|
elif self.__notes__ is not None:
|
||||||
yield "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
|
yield indent + "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
|
||||||
|
|
||||||
|
if self.exceptions and show_group:
|
||||||
|
for ex in self.exceptions:
|
||||||
|
yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1)
|
||||||
|
|
||||||
def _format_syntax_error(self, stype):
|
def _format_syntax_error(self, stype):
|
||||||
"""Format SyntaxError exceptions (internal helper)."""
|
"""Format SyntaxError exceptions (internal helper)."""
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add option to :func:`traceback.format_exception_only` to recurse into the
|
||||||
|
nested exception of a :exc:`BaseExceptionGroup`.
|
Loading…
Reference in New Issue