bpo-26389: Allow passing an exception object in the traceback module (GH-22610)
The format_exception(), format_exception_only(), and
print_exception() functions can now take an exception object as a positional-only argument.
Co-Authored-By: Matthias Bussonnier <bussonniermatthias@gmail.com>
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 730596e..91688ff 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -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):
diff --git a/Lib/traceback.py b/Lib/traceback.py
index a19e387..d2d93c8 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -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
@@ -95,9 +107,7 @@ 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(
@@ -105,7 +115,8 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True):
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
@@ -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
@@ -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.