Skip to content
Open
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
27 changes: 20 additions & 7 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,13 @@ class X(Exception):
def __str__(self):
1/0
err = traceback.format_exception_only(X, X())
self.assertEqual(len(err), 1)
self.assertEqual(len(err), 10)
str_value = '<exception str() failed>'
if X.__module__ in ('__main__', 'builtins'):
str_name = X.__qualname__
else:
str_name = '.'.join([X.__module__, X.__qualname__])
self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
self.assertIn("%s: %s\n" % (str_name, str_value), err[0])

def test_format_exception_group_without_show_group(self):
eg = ExceptionGroup('A', [ValueError('B')])
Expand Down Expand Up @@ -2482,7 +2482,10 @@ def __repr__(self):

e.__notes__ = Unprintable()
err_msg = '<__notes__ repr() failed>'
self.assertEqual(self.get_report(e), vanilla + err_msg + '\n')
ignore_msg = "Exception ignored in __notes__ repr():"
msg = self.get_report(e)
self.assertIn(vanilla + err_msg + '\n', msg)
self.assertIn(ignore_msg, msg)

# non-string item in the __notes__ sequence
e.__notes__ = [BadThing(), 'Final Note']
Expand All @@ -2492,7 +2495,9 @@ def __repr__(self):
# unprintable, non-string item in the __notes__ sequence
e.__notes__ = [Unprintable(), 'Final Note']
err_msg = '<note str() failed>'
self.assertEqual(self.get_report(e), vanilla + err_msg + '\nFinal Note\n')
msg = self.get_report(e)
self.assertIn(vanilla + err_msg + '\nFinal Note\n', msg)
self.assertIn("Exception ignored in note str():", msg)

e.__notes__ = "please do not explode me"
err_msg = "'please do not explode me'"
Expand Down Expand Up @@ -2602,7 +2607,9 @@ def __str__(self):
err = self.get_report(X())
str_value = '<exception str() failed>'
str_name = '.'.join([X.__module__, X.__qualname__])
self.assertEqual(MODULE_PREFIX + err, f"{str_name}: {str_value}\n")
ignore_sentence = "Exception ignored in exception str():"
self.assertIn(f"{str_name}: {str_value}\n", MODULE_PREFIX + err)
self.assertIn(ignore_sentence, err)


# #### Exception Groups ####
Expand Down Expand Up @@ -4226,8 +4233,14 @@ def __getattr__(self, attr):
raise AttributeError(23)

for cls in [A, B, C]:
actual = self.get_suggestion(cls(), 'bluch')
self.assertIn("blech", actual)
try:
getattr(cls(), "bluch")
except AttributeError:
msg = traceback.format_exc()
self.assertIn("blech", msg)
# actual = self.get_suggestion(cls(), 'bluch')
# self.assertIn("blech", actual)
# The above using is changed because it will get the warning in the ignore exception


class DelattrSuggestionTests(BaseSuggestionTests):
Expand Down
150 changes: 131 additions & 19 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import tokenize
import io
import _colorize
import threading

from contextlib import suppress

Expand Down Expand Up @@ -198,11 +199,89 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=
return line


def _safe_string(value, what, func=str):
def _remove_exception(exc_value, other_exc_value, _seen=None):
if _seen is None:
_seen = set()
if id(exc_value) not in _seen:
_seen.add(id(exc_value))
if isinstance(exc_value.__cause__, BaseException):
if exc_value.__cause__ is other_exc_value:
exc_value.__cause__ = None
elif isinstance(exc_value.__cause__, BaseExceptionGroup):
if other_exc_value in exc_value.__cause__.exceptions:
exc_value.__cause__ = None
else:
_remove_exception(exc_value.__cause__, other_exc_value, _seen)
for i in exc_value.__cause__.exceptions:
_remove_exception(i, other_exc_value, _seen)
else:
_remove_exception(exc_value.__cause__, other_exc_value, _seen)
if isinstance(exc_value.__context__, BaseException):
if exc_value.__context__ is other_exc_value:
exc_value.__context__ = None
elif isinstance(exc_value.__context__, BaseExceptionGroup):
if other_exc_value in exc_value.__context__.exceptions:
exc_value.__context__ = None
else:
_remove_exception(
exc_value.__context__, other_exc_value, _seen
)
for i in exc_value.__context__.exceptions:
_remove_exception(i, other_exc_value, _seen)
else:
_remove_exception(
exc_value.__context__, other_exc_value, _seen
)


def _traceback_to_tuples(tb):
extracted = extract_tb(tb)
return tuple(
(f.filename, f.lineno, getattr(f, "name", None), f.line)
for f in extracted
) # handle SyntaxError


def _safe_string(value, what, func=str,
exception_target=None, exception_exclude=None):
try:
return func(value)
except:
return f'<{what} {func.__name__}() failed>'
if isinstance(exception_target, list):
typ, val, tb = sys.exc_info()
_add_exception_note(typ, val, tb, f"{what} {func.__name__}()",
exception_target, exception_exclude)
return f"<{what} {func.__name__}() failed>"


_ADD_EXC_NOTE_LIMIT = 10


def _add_exception_note(exc_type, exc_value, exc_tb, where,
exception_target, exception_exclude=None, _seen=threading.local()):
if not hasattr(_seen, "_seen"):
_seen._seen = set()
if not hasattr(_seen, "times"):
_seen.times = 0
if not isinstance(exception_target, list):
return
_seen.times += 1
tb_tuple = _traceback_to_tuples(exc_tb)
if tb_tuple not in _seen._seen and _seen.times <= _ADD_EXC_NOTE_LIMIT:
_seen._seen.add(tb_tuple)
if exception_exclude is not None:
_remove_exception(exc_value, exception_exclude)
msg = "".join(TracebackException(exc_type, exc_value, exc_tb).format())
while msg.endswith("\n") or msg.endswith(" "):
msg = msg[:-1]
exception_target.append(
f"\nException ignored in {where}:"
)
exception_target.append(msg)
_seen.times -= 1
if _seen.times <= 0:
_seen.times = 0
_seen._seen.clear()

# --

Expand Down Expand Up @@ -1072,12 +1151,13 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=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')
try:
self.__notes__ = getattr(exc_value, '__notes__', None)
except Exception as e:
self.__notes__ = [
f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}']
exception_target = []
self._str = _safe_string(
exc_value,
"exception",
exception_target=exception_target,
exception_exclude=exc_value,
)

self._is_syntax_error = False
self._have_exc_type = exc_type is not None
Expand Down Expand Up @@ -1125,6 +1205,35 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
self._str += f" Or did you forget to import '{wrong_name}'?"
else:
self._str += f". Did you forget to import '{wrong_name}'?"
try:
original__notes__ = getattr(exc_value, "__notes__", None)
except Exception as e:
original__notes__ = [
f"Ignored error getting __notes__: {_safe_string(e, '__notes__', repr, exception_target, e)}"
]
if original__notes__ is not None and not (
isinstance(original__notes__, collections.abc.Sequence)
and not isinstance(original__notes__, (str, bytes))
):
original__notes__ = [
_safe_string(
original__notes__,
"__notes__",
repr,
exception_target,
exc_value,
)
]
final_string_list = []
if original__notes__ is not None: # avoid that __bool__ raise Exception
for i in original__notes__:
final_string_list.append(
_safe_string(
i, "note", str, exception_target, exc_value
)
)
self.__notes__ = final_string_list
self.exception_target = exception_target
if lookup_lines:
self._load_lines()
self.__suppress_context__ = \
Expand Down Expand Up @@ -1253,6 +1362,7 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs):
well, recursively, with indentation relative to their nesting depth.
"""
colorize = kwargs.get("colorize", False)
exception_target = kwargs.get("exception_target", True)

indent = 3 * _depth * ' '
if not self._have_exc_type:
Expand All @@ -1275,15 +1385,11 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs):
else:
yield from [indent + l for l in self._format_syntax_error(stype, colorize=colorize)]

if (
isinstance(self.__notes__, collections.abc.Sequence)
and not isinstance(self.__notes__, (str, bytes))
):
for note in self.__notes__:
note = _safe_string(note, 'note')
for note in self.__notes__:
yield from [indent + l + '\n' for l in note.split('\n')]
if exception_target:
for note in self.exception_target:
yield from [indent + l + '\n' for l in note.split('\n')]
elif self.__notes__ is not None:
yield indent + "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))

if self.exceptions and show_group:
for ex in self.exceptions:
Expand Down Expand Up @@ -1490,6 +1596,7 @@ def format(self, *, chain=True, _ctx=None, **kwargs):
string in the output.
"""
colorize = kwargs.get("colorize", False)
exception_target = kwargs.get("exception_target", True)
if _ctx is None:
_ctx = _ExceptionPrintContext()

Expand Down Expand Up @@ -1520,7 +1627,7 @@ def format(self, *, chain=True, _ctx=None, **kwargs):
if exc.stack:
yield from _ctx.emit('Traceback (most recent call last):\n')
yield from _ctx.emit(exc.stack.format(colorize=colorize))
yield from _ctx.emit(exc.format_exception_only(colorize=colorize))
yield from _ctx.emit(exc.format_exception_only(colorize=colorize, exception_target=exception_target))
elif _ctx.exception_group_depth > self.max_group_depth:
# exception group, but depth exceeds limit
yield from _ctx.emit(
Expand All @@ -1537,7 +1644,7 @@ def format(self, *, chain=True, _ctx=None, **kwargs):
margin_char = '+' if is_toplevel else None)
yield from _ctx.emit(exc.stack.format(colorize=colorize))

yield from _ctx.emit(exc.format_exception_only(colorize=colorize))
yield from _ctx.emit(exc.format_exception_only(colorize=colorize, exception_target=exception_target))
num_excs = len(exc.exceptions)
if num_excs <= self.max_group_width:
n = num_excs
Expand All @@ -1560,7 +1667,7 @@ def format(self, *, chain=True, _ctx=None, **kwargs):
f'+---------------- {title} ----------------\n')
_ctx.exception_group_depth += 1
if not truncated:
yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize)
yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize, exception_target=exception_target)
else:
remaining = num_excs - self.max_group_width
plural = 's' if remaining > 1 else ''
Expand Down Expand Up @@ -1629,6 +1736,11 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
return None


# add "exception_target=None"
# then we can handle the exception raised from suggestion
# use function "_add_exception_note"
# it won't add in gh-135660

def _compute_suggestion_error(exc_value, tb, wrong_name):
if wrong_name is None or not isinstance(wrong_name, str):
return None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Print the exception as warning (ignore) when handle a transmitted anomalies
Loading