bpo-26389: Allow passing an exception object in the traceback module by ZackerySpytz · Pull Request #22610 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions Doc/library/traceback.rst
18 changes: 18 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,15 @@ retrieve the functions set by :func:`threading.settrace` and
:func:`threading.setprofile` respectively.
(Contributed by Mario Corchero in :issue:`42251`.)

traceback
---------

The :func:`~traceback.format_exception`,
:func:`~traceback.format_exception_only`, and
:func:`~traceback.print_exception` functions can now take an exception object
as a positional-only argument.
(Contributed by Zackery Spytz and Matthias Bussonnier in :issue:`26389`.)

types
-----

Expand Down Expand Up @@ -328,6 +337,15 @@ This section lists previously described changes and other bugfixes
that may require changes to your code.


Changes in the Python API
-------------------------

* The *etype* parameters of the :func:`~traceback.format_exception`,
:func:`~traceback.format_exception_only`, and
:func:`~traceback.print_exception` functions in the :mod:`traceback` module
have been renamed to *exc*.
(Contributed by Zackery Spytz and Matthias Bussonnier in :issue:`26389`.)


Build Changes
=============
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,26 @@ def test_print_exception(self):
)
self.assertEqual(output.getvalue(), "Exception: projector\n")

def test_print_exception_exc(self):
output = StringIO()
traceback.print_exception(Exception("projector"), file=output)
self.assertEqual(output.getvalue(), "Exception: projector\n")

def test_format_exception_exc(self):
e = Exception("projector")
output = traceback.format_exception(e)
self.assertEqual(output, ["Exception: projector\n"])
with self.assertRaisesRegex(ValueError, 'Both or neither'):
traceback.format_exception(e.__class__, e)
with self.assertRaisesRegex(ValueError, 'Both or neither'):
traceback.format_exception(e.__class__, tb=e.__traceback__)
with self.assertRaisesRegex(TypeError, 'positional-only'):
traceback.format_exception(exc=e)

def test_format_exception_only_exc(self):
output = traceback.format_exception_only(Exception("projector"))
self.assertEqual(output, ["Exception: projector\n"])


class TracebackFormatTests(unittest.TestCase):

Expand Down
36 changes: 23 additions & 13 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,19 @@ def extract_tb(tb, limit=None):
"another exception occurred:\n\n")


def print_exception(etype, value, tb, limit=None, file=None, chain=True):
_sentinel = object()


def _parse_value_tb(exc, value, tb):
if (value is _sentinel) != (tb is _sentinel):
raise ValueError("Both or neither of value and tb must be given")
if value is tb is _sentinel:
return exc, exc.__traceback__
return value, tb


def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
file=None, chain=True):
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.

This differs from print_tb() in the following ways: (1) if
Expand All @@ -95,17 +107,16 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
occurred with a caret on the next line indicating the approximate
position of the error.
"""
# format_exception has ignored etype for some time, and code such as cgitb
# passes in bogus values as a result. For compatibility with such code we
# ignore it here (rather than in the new TracebackException API).
value, tb = _parse_value_tb(exc, value, tb)
if file is None:
file = sys.stderr
for line in TracebackException(
type(value), value, tb, limit=limit).format(chain=chain):
print(line, file=file, end="")


def format_exception(etype, value, tb, limit=None, chain=True):
def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
chain=True):
"""Format a stack trace and the exception information.

The arguments have the same meaning as the corresponding arguments
Expand All @@ -114,19 +125,15 @@ def format_exception(etype, value, tb, limit=None, chain=True):
these lines are concatenated and printed, exactly the same text is
printed as does print_exception().
"""
# format_exception has ignored etype for some time, and code such as cgitb
# passes in bogus values as a result. For compatibility with such code we
# ignore it here (rather than in the new TracebackException API).
value, tb = _parse_value_tb(exc, value, tb)
return list(TracebackException(
type(value), value, tb, limit=limit).format(chain=chain))


def format_exception_only(etype, value):
def format_exception_only(exc, /, value=_sentinel):
"""Format the exception part of a traceback.

The arguments are the exception type and value such as given by
sys.last_type and sys.last_value. The return value is a list of
strings, each ending in a newline.
The return value is a list of strings, each ending in a newline.

Normally, the list contains a single string; however, for
SyntaxError exceptions, it contains several lines that (when
Expand All @@ -137,7 +144,10 @@ def format_exception_only(etype, value):
string in the list.

"""
return list(TracebackException(etype, value, None).format_exception_only())
if value is _sentinel:
value = exc
return list(TracebackException(
type(value), value, None).format_exception_only())


# -- not official API but folk probably use these two functions.
Expand Down