Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-26389: Allow passing an exception object in the traceback module #22610

Merged
merged 4 commits into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ The module defines the following functions:
Added negative *limit* support.


.. function:: print_exception(etype, value, tb, limit=None, file=None, chain=True)
.. function:: print_exception(exc, /[, value, tb], limit=None, \
file=None, chain=True)

Print exception information and stack trace entries from traceback object
*tb* to *file*. This differs from :func:`print_tb` in the following
Expand All @@ -45,14 +46,18 @@ The module defines the following functions:
* if *tb* is not ``None``, it prints a header ``Traceback (most recent
call last):``

* it prints the exception *etype* and *value* after the stack trace
* it prints the exception type and *value* after the stack trace

.. index:: single: ^ (caret); marker

* if *type(value)* is :exc:`SyntaxError` and *value* has the appropriate
format, it prints the line where the syntax error occurred with a caret
indicating the approximate position of the error.

Since Python 3.10, instead of passing *value* and *tb*, an exception object
can be passed as the first argument. If *value* and *tb* are provided, the
first argument is ignored in order to provide backwards compatibility.

The optional *limit* argument has the same meaning as for :func:`print_tb`.
If *chain* is true (the default), then chained exceptions (the
:attr:`__cause__` or :attr:`__context__` attributes of the exception) will be
Expand All @@ -62,6 +67,10 @@ The module defines the following functions:
.. versionchanged:: 3.5
The *etype* argument is ignored and inferred from the type of *value*.

.. versionchanged:: 3.10
The *etype* parameter has been renamed to *exc* and is now
ZackerySpytz marked this conversation as resolved.
Show resolved Hide resolved
positional-only.


.. function:: print_exc(limit=None, file=None, chain=True)

Expand Down Expand Up @@ -121,18 +130,26 @@ The module defines the following functions:
text line is not ``None``.


.. function:: format_exception_only(etype, value)
.. function:: format_exception_only(exc, /[, value])

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
ending in a newline. Normally, the list contains a single string; however,
for :exc:`SyntaxError` exceptions, it contains several lines that (when
printed) display detailed information about where the syntax error occurred.
The message indicating which exception occurred is the always last string in
the list.

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. Normally,
the list contains a single string; however, for :exc:`SyntaxError`
exceptions, it contains several lines that (when printed) display detailed
information about where the syntax error occurred. The message indicating
which exception occurred is the always last string in the list.
Since Python 3.10, instead of passing *value*, an exception object
can be passed as the first argument. If *value* is provided, the first
argument is ignored in order to provide backwards compatibility.

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

.. function:: format_exception(etype, value, tb, limit=None, chain=True)

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

Format a stack trace and the exception information. The arguments have the
same meaning as the corresponding arguments to :func:`print_exception`. The
Expand All @@ -143,6 +160,10 @@ The module defines the following functions:
.. versionchanged:: 3.5
The *etype* argument is ignored and inferred from the type of *value*.

.. versionchanged:: 3.10
This function's behavior and signature were modified to match
:func:`print_exception`.


.. function:: format_exc(limit=None, chain=True)

Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,15 @@ Add :data:`sys.orig_argv` attribute: the list of the original command line
arguments passed to the Python executable.
(Contributed by Victor Stinner in :issue:`23427`.)

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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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.