aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Lib/traceback.py
diff options
context:
space:
mode:
authorshadchin <shadchin@yandex-team.com>2024-02-12 07:53:52 +0300
committershadchin <shadchin@yandex-team.com>2024-02-12 08:07:36 +0300
commitce1b7ca3171f9158180640c6a02a74b4afffedea (patch)
treee47c1e8391b1b0128262c1e9b1e6ed4c8fff2348 /contrib/tools/python3/src/Lib/traceback.py
parent57350d96f030db90f220ce50ee591d5c5d403df7 (diff)
downloadydb-ce1b7ca3171f9158180640c6a02a74b4afffedea.tar.gz
Update Python from 3.11.8 to 3.12.2
Diffstat (limited to 'contrib/tools/python3/src/Lib/traceback.py')
-rw-r--r--contrib/tools/python3/src/Lib/traceback.py189
1 files changed, 177 insertions, 12 deletions
diff --git a/contrib/tools/python3/src/Lib/traceback.py b/contrib/tools/python3/src/Lib/traceback.py
index e7026e545c..8247d8ff8c 100644
--- a/contrib/tools/python3/src/Lib/traceback.py
+++ b/contrib/tools/python3/src/Lib/traceback.py
@@ -176,20 +176,24 @@ def _safe_string(value, what, func=str):
# --
def print_exc(limit=None, file=None, chain=True):
- """Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
- print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
+ """Shorthand for 'print_exception(sys.exception(), limit, file, chain)'."""
+ print_exception(sys.exception(), limit=limit, file=file, chain=chain)
def format_exc(limit=None, chain=True):
"""Like print_exc() but return a string."""
- return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
+ return "".join(format_exception(sys.exception(), limit=limit, chain=chain))
def print_last(limit=None, file=None, chain=True):
- """This is a shorthand for 'print_exception(sys.last_type,
- sys.last_value, sys.last_traceback, limit, file)'."""
- if not hasattr(sys, "last_type"):
+ """This is a shorthand for 'print_exception(sys.last_exc, limit, file, chain)'."""
+ if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"):
raise ValueError("no last exception")
- print_exception(sys.last_type, sys.last_value, sys.last_traceback,
- limit, file, chain)
+
+ if hasattr(sys, "last_exc"):
+ print_exception(sys.last_exc, limit, file, chain)
+ else:
+ print_exception(sys.last_type, sys.last_value, sys.last_traceback,
+ limit, file, chain)
+
#
# Printing and Extracting Stacks.
@@ -276,7 +280,8 @@ class FrameSummary:
self._line = line
if lookup_line:
self.line
- self.locals = {k: repr(v) for k, v in locals.items()} if locals else None
+ self.locals = {k: _safe_string(v, 'local', func=repr)
+ for k, v in locals.items()} if locals else None
self.end_lineno = end_lineno
self.colno = colno
self.end_colno = end_colno
@@ -737,7 +742,7 @@ class TracebackException:
self.__notes__ = getattr(exc_value, '__notes__', None)
except Exception as e:
self.__notes__ = [
- f'Ignored error getting __notes__: {_safe_string(e, "__notes__", repr)}']
+ f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}']
if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
@@ -750,6 +755,25 @@ class TracebackException:
self.offset = exc_value.offset
self.end_offset = exc_value.end_offset
self.msg = exc_value.msg
+ 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)
+ suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
+ if suggestion:
+ self._str += f". Did you mean: '{suggestion}'?"
+ elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \
+ getattr(exc_value, "name", None) is not None:
+ wrong_name = getattr(exc_value, "name", None)
+ suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
+ if suggestion:
+ self._str += f". Did you mean: '{suggestion}'?"
+ if issubclass(exc_type, NameError):
+ wrong_name = getattr(exc_value, "name", None)
+ if wrong_name is not None and wrong_name in sys.stdlib_module_names:
+ if suggestion:
+ self._str += f" Or did you forget to import '{wrong_name}'"
+ else:
+ self._str += f". Did you forget to import '{wrong_name}'"
if lookup_lines:
self._load_lines()
self.__suppress_context__ = \
@@ -870,12 +894,16 @@ class TracebackException:
yield _format_final_exc_line(stype, self._str)
else:
yield from self._format_syntax_error(stype)
- if isinstance(self.__notes__, collections.abc.Sequence):
+
+ if (
+ isinstance(self.__notes__, collections.abc.Sequence)
+ and not isinstance(self.__notes__, (str, bytes))
+ ):
for note in self.__notes__:
note = _safe_string(note, 'note')
yield from [l + '\n' for l in note.split('\n')]
elif self.__notes__ is not None:
- yield _safe_string(self.__notes__, '__notes__', func=repr)
+ yield "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
def _format_syntax_error(self, stype):
"""Format SyntaxError exceptions (internal helper)."""
@@ -1020,3 +1048,140 @@ class TracebackException:
file = sys.stderr
for line in self.format(chain=chain):
print(line, file=file, end="")
+
+
+_MAX_CANDIDATE_ITEMS = 750
+_MAX_STRING_SIZE = 40
+_MOVE_COST = 2
+_CASE_COST = 1
+
+
+def _substitution_cost(ch_a, ch_b):
+ if ch_a == ch_b:
+ return 0
+ if ch_a.lower() == ch_b.lower():
+ return _CASE_COST
+ return _MOVE_COST
+
+
+def _compute_suggestion_error(exc_value, tb, wrong_name):
+ if wrong_name is None or not isinstance(wrong_name, str):
+ return None
+ if isinstance(exc_value, AttributeError):
+ obj = exc_value.obj
+ try:
+ d = dir(obj)
+ except Exception:
+ return None
+ elif isinstance(exc_value, ImportError):
+ try:
+ mod = __import__(exc_value.name)
+ d = dir(mod)
+ except Exception:
+ return None
+ else:
+ assert isinstance(exc_value, NameError)
+ # find most recent frame
+ if tb is None:
+ return None
+ while tb.tb_next is not None:
+ tb = tb.tb_next
+ frame = tb.tb_frame
+ d = (
+ list(frame.f_locals)
+ + list(frame.f_globals)
+ + list(frame.f_builtins)
+ )
+
+ # Check first if we are in a method and the instance
+ # has the wrong name as attribute
+ if 'self' in frame.f_locals:
+ self = frame.f_locals['self']
+ if hasattr(self, wrong_name):
+ return f"self.{wrong_name}"
+
+ # Compute closest match
+
+ if len(d) > _MAX_CANDIDATE_ITEMS:
+ return None
+ wrong_name_len = len(wrong_name)
+ if wrong_name_len > _MAX_STRING_SIZE:
+ return None
+ best_distance = wrong_name_len
+ suggestion = None
+ for possible_name in d:
+ if possible_name == wrong_name:
+ # A missing attribute is "found". Don't suggest it (see GH-88821).
+ continue
+ # No more than 1/3 of the involved characters should need changed.
+ max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
+ # Don't take matches we've already beaten.
+ max_distance = min(max_distance, best_distance - 1)
+ current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance)
+ if current_distance > max_distance:
+ continue
+ if not suggestion or current_distance < best_distance:
+ suggestion = possible_name
+ best_distance = current_distance
+ return suggestion
+
+
+def _levenshtein_distance(a, b, max_cost):
+ # A Python implementation of Python/suggestions.c:levenshtein_distance.
+
+ # Both strings are the same
+ if a == b:
+ return 0
+
+ # Trim away common affixes
+ pre = 0
+ while a[pre:] and b[pre:] and a[pre] == b[pre]:
+ pre += 1
+ a = a[pre:]
+ b = b[pre:]
+ post = 0
+ while a[:post or None] and b[:post or None] and a[post-1] == b[post-1]:
+ post -= 1
+ a = a[:post or None]
+ b = b[:post or None]
+ if not a or not b:
+ return _MOVE_COST * (len(a) + len(b))
+ if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE:
+ return max_cost + 1
+
+ # Prefer shorter buffer
+ if len(b) < len(a):
+ a, b = b, a
+
+ # Quick fail when a match is impossible
+ if (len(b) - len(a)) * _MOVE_COST > max_cost:
+ return max_cost + 1
+
+ # Instead of producing the whole traditional len(a)-by-len(b)
+ # matrix, we can update just one row in place.
+ # Initialize the buffer row
+ row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST))
+
+ result = 0
+ for bindex in range(len(b)):
+ bchar = b[bindex]
+ distance = result = bindex * _MOVE_COST
+ minimum = sys.maxsize
+ for index in range(len(a)):
+ # 1) Previous distance in this row is cost(b[:b_index], a[:index])
+ substitute = distance + _substitution_cost(bchar, a[index])
+ # 2) cost(b[:b_index], a[:index+1]) from previous row
+ distance = row[index]
+ # 3) existing result is cost(b[:b_index+1], a[index])
+
+ insert_delete = min(result, distance) + _MOVE_COST
+ result = min(insert_delete, substitute)
+
+ # cost(b[:b_index+1], a[:index+1])
+ row[index] = result
+ if result < minimum:
+ minimum = result
+ if minimum > max_cost:
+ # Everything in this row is too big, so bail early.
+ return max_cost + 1
+ return result