Skip to content

Commit

Permalink
pythongh-111388: Add show_group to traceback.format_exception_only
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Oct 27, 2023
1 parent 7f9a99e commit a3155cf
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 5 deletions.
9 changes: 8 additions & 1 deletion Doc/library/traceback.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ The module defines the following functions:
text line is not ``None``.


.. function:: format_exception_only(exc, /[, value])
.. function:: format_exception_only(exc, /[, value], *, show_group=False)

Format the exception part of a traceback using an exception value such as
given by ``sys.last_value``. The return value is a list of strings, each
Expand All @@ -149,13 +149,20 @@ The module defines the following functions:
can be passed as the first argument. If *value* is provided, the first
argument is ignored in order to provide backwards compatibility.

When *show_group* is ``True``, and the exception is an instance of
:exc:`BaseExceptionGroup`, the nested exceptions are included as
well, recursively, with indentation relative to their nesting depth.

.. versionchanged:: 3.10
The *etype* parameter has been renamed to *exc* and is now
positional-only.

.. versionchanged:: 3.11
The returned list now includes any notes attached to the exception.

.. versionchanged:: 3.13
*show_group* parameter was added.


.. function:: format_exception(exc, /[, value, tb], limit=None, chain=True)

Expand Down
133 changes: 132 additions & 1 deletion Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,137 @@ def __str__(self):
str_name = '.'.join([X.__module__, X.__qualname__])
self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))

def test_format_exception_group_without_show_group(self):
eg = ExceptionGroup('A', [ValueError('B')])
err = traceback.format_exception_only(eg)
self.assertEqual(err, ['ExceptionGroup: A (1 sub-exception)\n'])

def test_format_exception_group(self):
eg = ExceptionGroup('A', [ValueError('B')])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (1 sub-exception)\n',
' ValueError: B\n',
])

def test_format_base_exception_group(self):
eg = BaseExceptionGroup('A', [BaseException('B')])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'BaseExceptionGroup: A (1 sub-exception)\n',
' BaseException: B\n',
])

def test_format_exception_group_with_note(self):
exc = ValueError('B')
exc.add_note('Note')
eg = ExceptionGroup('A', [exc])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (1 sub-exception)\n',
' ValueError: B\n',
' Note\n',
])

def test_format_exception_group_explicit_class(self):
eg = ExceptionGroup('A', [ValueError('B')])
err = traceback.format_exception_only(ExceptionGroup, eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (1 sub-exception)\n',
' ValueError: B\n',
])

def test_format_exception_group_multiple_exceptions(self):
eg = ExceptionGroup('A', [ValueError('B'), TypeError('C')])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (2 sub-exceptions)\n',
' ValueError: B\n',
' TypeError: C\n',
])

def test_format_exception_group_multiline_messages(self):
eg = ExceptionGroup('A\n1', [ValueError('B\n2')])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A\n1 (1 sub-exception)\n',
' ValueError: B\n',
' 2\n',
])

def test_format_exception_group_syntax_error(self):
exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
eg = ExceptionGroup('A\n1', [exc])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A\n1 (1 sub-exception)\n',
' File "x.py", line 23\n',
' bad syntax\n',
' SyntaxError: error\n',
])

def test_format_exception_group_nested_with_notes(self):
exc = IndexError('D')
exc.add_note('Note\nmultiline')
eg = ExceptionGroup('A', [
ValueError('B'),
ExceptionGroup('C', [exc, LookupError('E')]),
TypeError('F'),
])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (3 sub-exceptions)\n',
' ValueError: B\n',
' ExceptionGroup: C (2 sub-exceptions)\n',
' IndexError: D\n',
' Note\n',
' multiline\n',
' LookupError: E\n',
' TypeError: F\n',
])

def test_format_exception_group_with_tracebacks(self):
def f():
try:
1 / 0
except ZeroDivisionError as e:
return e

def g():
try:
raise TypeError('g')
except TypeError as e:
return e

eg = ExceptionGroup('A', [
f(),
ExceptionGroup('B', [g()]),
])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (2 sub-exceptions)\n',
' ZeroDivisionError: division by zero\n',
' ExceptionGroup: B (1 sub-exception)\n',
' TypeError: g\n',
])

def test_format_exception_group_with_cause(self):
def f():
try:
try:
1 / 0
except ZeroDivisionError:
raise ValueError(0)
except Exception as e:
return e

eg = ExceptionGroup('A', [f()])
err = traceback.format_exception_only(eg, show_group=True)
self.assertEqual(err, [
'ExceptionGroup: A (1 sub-exception)\n',
' ValueError: 0\n',
])

@requires_subprocess()
def test_encoded_file(self):
# Test that tracebacks are correctly printed for encoded source files:
Expand Down Expand Up @@ -381,7 +512,7 @@ def test_signatures(self):

self.assertEqual(
str(inspect.signature(traceback.format_exception_only)),
'(exc, /, value=<implicit>)')
'(exc, /, value=<implicit>, *, show_group=False)')


class PurePythonExceptionFormattingMixin:
Expand Down
22 changes: 19 additions & 3 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
return list(te.format(chain=chain))


def format_exception_only(exc, /, value=_sentinel):
def format_exception_only(exc, /, value=_sentinel, *, show_group=False):
"""Format the exception part of a traceback.
The return value is a list of strings, each ending in a newline.
Expand All @@ -158,11 +158,15 @@ def format_exception_only(exc, /, value=_sentinel):
contains several lines that (when printed) display detailed information
about where the syntax error occurred. Following the message, the list
contains the exception's ``__notes__``.
When *show_group* is ``True``, and the exception is an instance of
:exc:`BaseExceptionGroup`, the nested exceptions are included as
well, recursively, with indentation relative to their nesting depth.
"""
if value is _sentinel:
value = exc
te = TracebackException(type(value), value, None, compact=True)
return list(te.format_exception_only())
return list(te.format_exception_only(show_group=show_group))


# -- not official API but folk probably use these two functions.
Expand Down Expand Up @@ -889,6 +893,10 @@ def format_exception_only(self, *, show_group=False, _depth=0):
display detailed information about where the syntax error occurred.
Following the message, generator also yields
all the exception's ``__notes__``.
When *show_group* is ``True``, and the exception is an instance of
:exc:`BaseExceptionGroup`, the nested exceptions are included as
well, recursively, with indentation relative to their nesting depth.
"""

indent = 3 * _depth * ' '
Expand All @@ -904,7 +912,15 @@ def format_exception_only(self, *, show_group=False, _depth=0):
stype = smod + '.' + stype

if not issubclass(self.exc_type, SyntaxError):
yield indent + _format_final_exc_line(stype, self._str)
if _depth > 0:
# Nested exceptions needs correct handling of multiline messages.
yield from [
indent + l + '\n'
for l in _format_final_exc_line(stype, self._str).split('\n')
if l
]
else:
yield _format_final_exc_line(stype, self._str)
else:
yield from [indent + l for l in self._format_syntax_error(stype)]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add ``show_group`` parameter to :func:`traceback.format_exception_only`,
which allows to format :exception:`ExceptionGroup` instances.

0 comments on commit a3155cf

Please sign in to comment.