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

gh-112332: Deprecate TracebackException.exc_type, add exc_type_str. #112333

Merged
merged 5 commits into from
Nov 28, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/library/traceback.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@ capture data for later printing in a lightweight fashion.

The class of the original traceback.

.. deprecated:: 3.13

.. attribute:: exc_type_str

String display of the class of the original exception.

.. versionadded:: 3.13

.. attribute:: filename

For syntax errors - the file name where the error occurred.
Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,12 @@ traceback
to format the nested exceptions of a :exc:`BaseExceptionGroup` instance, recursively.
(Contributed by Irit Katriel in :gh:`105292`.)

* Add the field *exc_type_str* to :class:`~traceback.TracebackException`, which
holds a string display of the *exc_type*. Deprecate the field *exc_type*
which holds the type object itself. Add parameter *save_exc_type* (default
``True``) to indicate whether ``exc_type`` should be saved.
(Contributed by Irit Katriel in :gh:`112332`.)

typing
------

Expand Down Expand Up @@ -367,6 +373,11 @@ Deprecated
security and functionality bugs. This includes removal of the ``--cgi``
flag to the ``python -m http.server`` command line in 3.15.

* :mod:`traceback`:

* The field *exc_type* of :class:`traceback.TracebackException` is
deprecated. Use *exc_type_str* instead.

* :mod:`typing`:

* Creating a :class:`typing.NamedTuple` class using keyword arguments to denote
Expand Down
51 changes: 43 additions & 8 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -2715,9 +2715,9 @@ def __repr__(self) -> str:

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)
Expand All @@ -2727,9 +2727,23 @@ def test_smoke(self):
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():
Expand All @@ -2750,7 +2764,9 @@ def foo():
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):
Expand All @@ -2772,7 +2788,9 @@ def test_cause(self):
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):
Expand All @@ -2792,7 +2810,9 @@ def test_context(self):
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):
Expand Down Expand Up @@ -2837,7 +2857,9 @@ def test_compact_with_cause(self):
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):
Expand All @@ -2857,9 +2879,22 @@ def test_compact_no_cause(self):
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
Expand Down
50 changes: 38 additions & 12 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import linecache
import sys
import textwrap
import warnings
from contextlib import suppress

__all__ = ['extract_stack', 'extract_tb', 'format_exception',
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -754,12 +756,23 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
_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
Expand All @@ -771,6 +784,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
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)
Expand Down Expand Up @@ -869,6 +883,24 @@ def from_exception(cls, exc, *args, **kwargs):
"""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:
Expand Down Expand Up @@ -901,18 +933,12 @@ def format_exception_only(self, *, show_group=False, _depth=0):
"""

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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Deprecate the ``exc_type`` field of :class:`traceback.TracebackException`.
Add ``exc_type_str`` to replace it.