mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
update Lib/traceback.py from CPython v3.10.5
This commit is contained in:
1 parent
5983a5aa0a
commit
8f5a00b31d
1 file changed
+169
-88
Vendored
+169
-88
@@ -84,7 +84,25 @@ _context_message = (
|
||||
"another exception occurred:\n\n")
|
||||
|
||||
|
||||
def print_exception(etype, value, tb, limit=None, file=None, chain=True):
|
||||
class _Sentinel:
|
||||
def __repr__(self):
|
||||
return "<implicit>"
|
||||
|
||||
_sentinel = _Sentinel()
|
||||
|
||||
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:
|
||||
if exc is not None:
|
||||
return exc, exc.__traceback__
|
||||
else:
|
||||
return None, None
|
||||
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,17 +113,16 @@ 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(
|
||||
type(value), value, tb, limit=limit).format(chain=chain):
|
||||
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
|
||||
for line in te.format(chain=chain):
|
||||
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 +131,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).
|
||||
return list(TracebackException(
|
||||
type(value), value, tb, limit=limit).format(chain=chain))
|
||||
value, tb = _parse_value_tb(exc, value, tb)
|
||||
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
|
||||
return list(te.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 +150,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
|
||||
te = TracebackException(type(value), value, None, compact=True)
|
||||
return list(te.format_exception_only())
|
||||
|
||||
|
||||
# -- not official API but folk probably use these two functions.
|
||||
@@ -279,12 +295,16 @@ class FrameSummary:
|
||||
return "<FrameSummary file {filename}, line {lineno} in {name}>".format(
|
||||
filename=self.filename, lineno=self.lineno, name=self.name)
|
||||
|
||||
def __len__(self):
|
||||
return 4
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
if self._line is None:
|
||||
self._line = linecache.getline(self.filename, self.lineno).strip()
|
||||
return self._line
|
||||
|
||||
if self.lineno is None:
|
||||
return None
|
||||
self._line = linecache.getline(self.filename, self.lineno)
|
||||
return self._line.strip()
|
||||
|
||||
def walk_stack(f):
|
||||
"""Walk a stack yielding the frame and line number for each frame.
|
||||
@@ -455,53 +475,29 @@ class TracebackException:
|
||||
occurred.
|
||||
- :attr:`lineno` For syntax errors - the linenumber where the error
|
||||
occurred.
|
||||
- :attr:`end_lineno` For syntax errors - the end linenumber where the error
|
||||
occurred. Can be `None` if not present.
|
||||
- :attr:`text` For syntax errors - the text where the error
|
||||
occurred.
|
||||
- :attr:`offset` For syntax errors - the offset into the text where the
|
||||
error occurred.
|
||||
- :attr:`end_offset` For syntax errors - the offset into the text where the
|
||||
error occurred. Can be `None` if not present.
|
||||
- :attr:`msg` For syntax errors - the compiler error message.
|
||||
"""
|
||||
|
||||
def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
|
||||
lookup_lines=True, capture_locals=False, _seen=None):
|
||||
lookup_lines=True, capture_locals=False, compact=False,
|
||||
_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.
|
||||
# Handle loops in __cause__ or __context__.
|
||||
is_recursive_call = _seen is not None
|
||||
if _seen is None:
|
||||
_seen = set()
|
||||
_seen.add(id(exc_value))
|
||||
# Gracefully handle (the way Python 2.4 and earlier did) the case of
|
||||
# being called with no type or value (None, None, None).
|
||||
if (exc_value and exc_value.__cause__ is not None
|
||||
and id(exc_value.__cause__) not in _seen):
|
||||
cause = TracebackException(
|
||||
type(exc_value.__cause__),
|
||||
exc_value.__cause__,
|
||||
exc_value.__cause__.__traceback__,
|
||||
limit=limit,
|
||||
lookup_lines=False,
|
||||
capture_locals=capture_locals,
|
||||
_seen=_seen)
|
||||
else:
|
||||
cause = None
|
||||
if (exc_value and exc_value.__context__ is not None
|
||||
and id(exc_value.__context__) not in _seen):
|
||||
context = TracebackException(
|
||||
type(exc_value.__context__),
|
||||
exc_value.__context__,
|
||||
exc_value.__context__.__traceback__,
|
||||
limit=limit,
|
||||
lookup_lines=False,
|
||||
capture_locals=capture_locals,
|
||||
_seen=_seen)
|
||||
else:
|
||||
context = None
|
||||
self.exc_traceback = exc_traceback
|
||||
self.__cause__ = cause
|
||||
self.__context__ = context
|
||||
self.__suppress_context__ = \
|
||||
exc_value.__suppress_context__ if exc_value else False
|
||||
|
||||
# TODO: locals.
|
||||
self.stack = StackSummary.extract(
|
||||
walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines,
|
||||
@@ -513,12 +509,62 @@ class TracebackException:
|
||||
if exc_type and issubclass(exc_type, SyntaxError):
|
||||
# Handle SyntaxError's specially
|
||||
self.filename = exc_value.filename
|
||||
self.lineno = str(exc_value.lineno)
|
||||
lno = exc_value.lineno
|
||||
self.lineno = str(lno) if lno is not None else None
|
||||
end_lno = exc_value.end_lineno
|
||||
self.end_lineno = str(end_lno) if end_lno is not None else None
|
||||
self.text = exc_value.text
|
||||
self.offset = exc_value.offset
|
||||
self.end_offset = exc_value.end_offset
|
||||
self.msg = exc_value.msg
|
||||
if lookup_lines:
|
||||
self._load_lines()
|
||||
self.__suppress_context__ = \
|
||||
exc_value.__suppress_context__ if exc_value is not None else False
|
||||
|
||||
# Convert __cause__ and __context__ to `TracebackExceptions`s, use a
|
||||
# queue to avoid recursion (only the top-level call gets _seen == None)
|
||||
if not is_recursive_call:
|
||||
queue = [(self, exc_value)]
|
||||
while queue:
|
||||
te, e = queue.pop()
|
||||
if (e and e.__cause__ is not None
|
||||
and id(e.__cause__) not in _seen):
|
||||
cause = TracebackException(
|
||||
type(e.__cause__),
|
||||
e.__cause__,
|
||||
e.__cause__.__traceback__,
|
||||
limit=limit,
|
||||
lookup_lines=lookup_lines,
|
||||
capture_locals=capture_locals,
|
||||
_seen=_seen)
|
||||
else:
|
||||
cause = None
|
||||
|
||||
if compact:
|
||||
need_context = (cause is None and
|
||||
e is not None and
|
||||
not e.__suppress_context__)
|
||||
else:
|
||||
need_context = True
|
||||
if (e and e.__context__ is not None
|
||||
and need_context and id(e.__context__) not in _seen):
|
||||
context = TracebackException(
|
||||
type(e.__context__),
|
||||
e.__context__,
|
||||
e.__context__.__traceback__,
|
||||
limit=limit,
|
||||
lookup_lines=lookup_lines,
|
||||
capture_locals=capture_locals,
|
||||
_seen=_seen)
|
||||
else:
|
||||
context = None
|
||||
te.__cause__ = cause
|
||||
te.__context__ = context
|
||||
if cause:
|
||||
queue.append((te.__cause__, e.__cause__))
|
||||
if context:
|
||||
queue.append((te.__context__, e.__context__))
|
||||
|
||||
@classmethod
|
||||
def from_exception(cls, exc, *args, **kwargs):
|
||||
@@ -529,13 +575,11 @@ class TracebackException:
|
||||
"""Private API. force all lines in the stack to be loaded."""
|
||||
for frame in self.stack:
|
||||
frame.line
|
||||
if self.__context__:
|
||||
self.__context__._load_lines()
|
||||
if self.__cause__:
|
||||
self.__cause__._load_lines()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
if isinstance(other, TracebackException):
|
||||
return self.__dict__ == other.__dict__
|
||||
return NotImplemented
|
||||
|
||||
def __str__(self):
|
||||
return self._str
|
||||
@@ -546,7 +590,7 @@ class TracebackException:
|
||||
The return value is a generator of strings, each ending in a newline.
|
||||
|
||||
Normally, the generator emits a single string; however, for
|
||||
SyntaxError exceptions, it emites several lines that (when
|
||||
SyntaxError exceptions, it emits several lines that (when
|
||||
printed) display detailed information about where the syntax
|
||||
error occurred.
|
||||
|
||||
@@ -560,30 +604,50 @@ class TracebackException:
|
||||
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):
|
||||
yield _format_final_exc_line(stype, self._str)
|
||||
return
|
||||
else:
|
||||
yield from self._format_syntax_error(stype)
|
||||
|
||||
# It was a syntax error; show exactly where the problem was found.
|
||||
filename = self.filename or "<string>"
|
||||
lineno = str(self.lineno) or '?'
|
||||
yield ' File "{}", line {}\n'.format(filename, lineno)
|
||||
def _format_syntax_error(self, stype):
|
||||
"""Format SyntaxError exceptions (internal helper)."""
|
||||
# Show exactly where the problem was found.
|
||||
filename_suffix = ''
|
||||
if self.lineno is not None:
|
||||
yield ' File "{}", line {}\n'.format(
|
||||
self.filename or "<string>", self.lineno)
|
||||
elif self.filename is not None:
|
||||
filename_suffix = ' ({})'.format(self.filename)
|
||||
|
||||
badline = self.text
|
||||
offset = self.offset
|
||||
if badline is not None:
|
||||
yield ' {}\n'.format(badline.strip())
|
||||
if offset is not None:
|
||||
caretspace = badline.rstrip('\n')
|
||||
offset = min(len(caretspace), offset) - 1
|
||||
caretspace = caretspace[:offset].lstrip()
|
||||
# non-space whitespace (likes tabs) must be kept for alignment
|
||||
caretspace = ((c.isspace() and c or ' ') for c in caretspace)
|
||||
yield ' {}^\n'.format(''.join(caretspace))
|
||||
text = self.text
|
||||
if text is not None:
|
||||
# text = " foo\n"
|
||||
# rtext = " foo"
|
||||
# ltext = "foo"
|
||||
rtext = text.rstrip('\n')
|
||||
ltext = rtext.lstrip(' \n\f')
|
||||
spaces = len(rtext) - len(ltext)
|
||||
yield ' {}\n'.format(ltext)
|
||||
|
||||
if self.offset is not None:
|
||||
offset = self.offset
|
||||
end_offset = self.end_offset if self.end_offset not in {None, 0} else offset
|
||||
if offset == end_offset or end_offset == -1:
|
||||
end_offset = offset + 1
|
||||
|
||||
# Convert 1-based column offset to 0-based index into stripped text
|
||||
colno = offset - 1 - spaces
|
||||
end_colno = end_offset - 1 - spaces
|
||||
if colno >= 0:
|
||||
# non-space whitespace (likes tabs) must be kept for alignment
|
||||
caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno])
|
||||
yield ' {}{}'.format("".join(caretspace), ('^' * (end_colno - colno) + "\n"))
|
||||
msg = self.msg or "<no detail available>"
|
||||
yield "{}: {}\n".format(stype, msg)
|
||||
yield "{}: {}{}\n".format(stype, msg, filename_suffix)
|
||||
|
||||
def format(self, *, chain=True):
|
||||
"""Format the exception.
|
||||
@@ -597,15 +661,32 @@ class TracebackException:
|
||||
The message indicating which exception occurred is always the last
|
||||
string in the output.
|
||||
"""
|
||||
if chain:
|
||||
if self.__cause__ is not None:
|
||||
yield from self.__cause__.format(chain=chain)
|
||||
yield _cause_message
|
||||
elif (self.__context__ is not None and
|
||||
not self.__suppress_context__):
|
||||
yield from self.__context__.format(chain=chain)
|
||||
yield _context_message
|
||||
if self.exc_traceback is not None:
|
||||
yield 'Traceback (most recent call last):\n'
|
||||
yield from self.stack.format()
|
||||
yield from self.format_exception_only()
|
||||
|
||||
output = []
|
||||
exc = self
|
||||
while exc:
|
||||
if chain:
|
||||
if exc.__cause__ is not None:
|
||||
chained_msg = _cause_message
|
||||
chained_exc = exc.__cause__
|
||||
elif (exc.__context__ is not None and
|
||||
not exc.__suppress_context__):
|
||||
chained_msg = _context_message
|
||||
chained_exc = exc.__context__
|
||||
else:
|
||||
chained_msg = None
|
||||
chained_exc = None
|
||||
|
||||
output.append((chained_msg, exc))
|
||||
exc = chained_exc
|
||||
else:
|
||||
output.append((None, exc))
|
||||
exc = None
|
||||
|
||||
for msg, exc in reversed(output):
|
||||
if msg is not None:
|
||||
yield msg
|
||||
if exc.stack:
|
||||
yield 'Traceback (most recent call last):\n'
|
||||
yield from exc.stack.format()
|
||||
yield from exc.format_exception_only()
|
||||
Reference in new issue
Block a user