summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_traceback.py51
-rw-r--r--Lib/traceback.py50
2 files changed, 81 insertions, 20 deletions
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index b43dca6..c58d979 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -2715,9 +2715,9 @@ class Unrepresentable:
class TestTracebackException(unittest.TestCase):
- def test_smoke(self):
+ def do_test_smoke(self, exc, expected_type_str):
try:
- 1/0
+ raise exc
except Exception as e:
exc_obj = e
exc = traceback.TracebackException.from_exception(e)
@@ -2727,9 +2727,23 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(None, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(type(exc_obj), exc.exc_type)
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(expected_type_str, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
+ def test_smoke_builtin(self):
+ self.do_test_smoke(ValueError(42), 'ValueError')
+
+ def test_smoke_user_exception(self):
+ class MyException(Exception):
+ pass
+
+ self.do_test_smoke(
+ MyException('bad things happened'),
+ ('test.test_traceback.TestTracebackException.'
+ 'test_smoke_user_exception.<locals>.MyException'))
+
def test_from_exception(self):
# Check all the parameters are accepted.
def foo():
@@ -2750,7 +2764,9 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(None, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(type(exc_obj), exc.exc_type)
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
def test_cause(self):
@@ -2772,7 +2788,9 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(True, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(type(exc_obj), exc.exc_type)
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
def test_context(self):
@@ -2792,7 +2810,9 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(type(exc_obj), exc.exc_type)
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
def test_long_context_chain(self):
@@ -2837,7 +2857,9 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(None, exc.__context__)
self.assertEqual(True, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(type(exc_obj), exc.exc_type)
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
def test_compact_no_cause(self):
@@ -2857,9 +2879,22 @@ class TestTracebackException(unittest.TestCase):
self.assertEqual(exc_context, exc.__context__)
self.assertEqual(False, exc.__suppress_context__)
self.assertEqual(expected_stack, exc.stack)
- self.assertEqual(type(exc_obj), exc.exc_type)
+ with self.assertWarns(DeprecationWarning):
+ self.assertEqual(type(exc_obj), exc.exc_type)
+ self.assertEqual(type(exc_obj).__name__, exc.exc_type_str)
self.assertEqual(str(exc_obj), str(exc))
+ def test_no_save_exc_type(self):
+ try:
+ 1/0
+ except Exception as e:
+ exc = e
+
+ te = traceback.TracebackException.from_exception(
+ exc, save_exc_type=False)
+ with self.assertWarns(DeprecationWarning):
+ self.assertIsNone(te.exc_type)
+
def test_no_refs_to_exception_and_traceback_objects(self):
try:
1/0
diff --git a/Lib/traceback.py b/Lib/traceback.py
index b25a729..5d83f85 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -5,6 +5,7 @@ import itertools
import linecache
import sys
import textwrap
+import warnings
from contextlib import suppress
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
@@ -719,7 +720,8 @@ class TracebackException:
- :attr:`__suppress_context__` The *__suppress_context__* value from the
original exception.
- :attr:`stack` A `StackSummary` representing the traceback.
- - :attr:`exc_type` The class of the original traceback.
+ - :attr:`exc_type` (deprecated) The class of the original traceback.
+ - :attr:`exc_type_str` String display of exc_type
- :attr:`filename` For syntax errors - the filename where the error
occurred.
- :attr:`lineno` For syntax errors - the linenumber where the error
@@ -737,7 +739,7 @@ class TracebackException:
def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
lookup_lines=True, capture_locals=False, compact=False,
- max_group_width=15, max_group_depth=10, _seen=None):
+ max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None):
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
# permit backwards compat with the existing API, otherwise we
# need stub thunk objects just to glue it together.
@@ -754,12 +756,23 @@ class TracebackException:
_walk_tb_with_full_positions(exc_traceback),
limit=limit, lookup_lines=lookup_lines,
capture_locals=capture_locals)
- self.exc_type = exc_type
+
+ self._exc_type = exc_type if save_exc_type else None
+
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
self._str = _safe_string(exc_value, 'exception')
self.__notes__ = getattr(exc_value, '__notes__', None)
+ self._is_syntax_error = False
+ self._have_exc_type = exc_type is not None
+ if exc_type is not None:
+ self.exc_type_qualname = exc_type.__qualname__
+ self.exc_type_module = exc_type.__module__
+ else:
+ self.exc_type_qualname = None
+ self.exc_type_module = None
+
if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
self.filename = exc_value.filename
@@ -771,6 +784,7 @@ class TracebackException:
self.offset = exc_value.offset
self.end_offset = exc_value.end_offset
self.msg = exc_value.msg
+ self._is_syntax_error = True
elif exc_type and issubclass(exc_type, ImportError) and \
getattr(exc_value, "name_from", None) is not None:
wrong_name = getattr(exc_value, "name_from", None)
@@ -869,6 +883,24 @@ class TracebackException:
"""Create a TracebackException from an exception."""
return cls(type(exc), exc, exc.__traceback__, *args, **kwargs)
+ @property
+ def exc_type(self):
+ warnings.warn('Deprecated in 3.13. Use exc_type_str instead.',
+ DeprecationWarning, stacklevel=2)
+ return self._exc_type
+
+ @property
+ def exc_type_str(self):
+ if not self._have_exc_type:
+ return None
+ stype = self.exc_type_qualname
+ smod = self.exc_type_module
+ if smod not in ("__main__", "builtins"):
+ if not isinstance(smod, str):
+ smod = "<unknown>"
+ stype = smod + '.' + stype
+ return stype
+
def _load_lines(self):
"""Private API. force all lines in the stack to be loaded."""
for frame in self.stack:
@@ -901,18 +933,12 @@ class TracebackException:
"""
indent = 3 * _depth * ' '
- if self.exc_type is None:
+ if not self._have_exc_type:
yield indent + _format_final_exc_line(None, self._str)
return
- stype = self.exc_type.__qualname__
- smod = self.exc_type.__module__
- if smod not in ("__main__", "builtins"):
- if not isinstance(smod, str):
- smod = "<unknown>"
- stype = smod + '.' + stype
-
- if not issubclass(self.exc_type, SyntaxError):
+ stype = self.exc_type_str
+ if not self._is_syntax_error:
if _depth > 0:
# Nested exceptions needs correct handling of multiline messages.
formatted = _format_final_exc_line(