1 """Extract, format and print information about Python stack traces."""
2
3 import collections.abc
4 import itertools
5 import linecache
6 import sys
7 import textwrap
8 from contextlib import suppress
9
10 __all__ = ['extract_stack', 'extract_tb', 'format_exception',
11 'format_exception_only', 'format_list', 'format_stack',
12 'format_tb', 'print_exc', 'format_exc', 'print_exception',
13 'print_last', 'print_stack', 'print_tb', 'clear_frames',
14 'FrameSummary', 'StackSummary', 'TracebackException',
15 'walk_stack', 'walk_tb']
16
17 #
18 # Formatting and printing lists of traceback lines.
19 #
20
21 def print_list(extracted_list, file=None):
22 """Print the list of tuples as returned by extract_tb() or
23 extract_stack() as a formatted stack trace to the given file."""
24 if file is None:
25 file = sys.stderr
26 for item in StackSummary.from_list(extracted_list).format():
27 print(item, file=file, end="")
28
29 def format_list(extracted_list):
30 """Format a list of tuples or FrameSummary objects for printing.
31
32 Given a list of tuples or FrameSummary objects as returned by
33 extract_tb() or extract_stack(), return a list of strings ready
34 for printing.
35
36 Each string in the resulting list corresponds to the item with the
37 same index in the argument list. Each string ends in a newline;
38 the strings may contain internal newlines as well, for those items
39 whose source text line is not None.
40 """
41 return StackSummary.from_list(extracted_list).format()
42
43 #
44 # Printing and Extracting Tracebacks.
45 #
46
47 def print_tb(tb, limit=None, file=None):
48 """Print up to 'limit' stack trace entries from the traceback 'tb'.
49
50 If 'limit' is omitted or None, all entries are printed. If 'file'
51 is omitted or None, the output goes to sys.stderr; otherwise
52 'file' should be an open file or file-like object with a write()
53 method.
54 """
55 print_list(extract_tb(tb, limit=limit), file=file)
56
57 def format_tb(tb, limit=None):
58 """A shorthand for 'format_list(extract_tb(tb, limit))'."""
59 return extract_tb(tb, limit=limit).format()
60
61 def extract_tb(tb, limit=None):
62 """
63 Return a StackSummary object representing a list of
64 pre-processed entries from traceback.
65
66 This is useful for alternate formatting of stack traces. If
67 'limit' is omitted or None, all entries are extracted. A
68 pre-processed stack trace entry is a FrameSummary object
69 containing attributes filename, lineno, name, and line
70 representing the information that is usually printed for a stack
71 trace. The line is a string with leading and trailing
72 whitespace stripped; if the source is not available it is None.
73 """
74 return StackSummary._extract_from_extended_frame_gen(
75 _walk_tb_with_full_positions(tb), limit=limit)
76
77 #
78 # Exception formatting and output.
79 #
80
81 _cause_message = (
82 "\nThe above exception was the direct cause "
83 "of the following exception:\n\n")
84
85 _context_message = (
86 "\nDuring handling of the above exception, "
87 "another exception occurred:\n\n")
88
89
90 class ESC[4;38;5;81m_Sentinel:
91 def __repr__(self):
92 return "<implicit>"
93
94 _sentinel = _Sentinel()
95
96 def _parse_value_tb(exc, value, tb):
97 if (value is _sentinel) != (tb is _sentinel):
98 raise ValueError("Both or neither of value and tb must be given")
99 if value is tb is _sentinel:
100 if exc is not None:
101 if isinstance(exc, BaseException):
102 return exc, exc.__traceback__
103
104 raise TypeError(f'Exception expected for value, '
105 f'{type(exc).__name__} found')
106 else:
107 return None, None
108 return value, tb
109
110
111 def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
112 file=None, chain=True):
113 """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
114
115 This differs from print_tb() in the following ways: (1) if
116 traceback is not None, it prints a header "Traceback (most recent
117 call last):"; (2) it prints the exception type and value after the
118 stack trace; (3) if type is SyntaxError and value has the
119 appropriate format, it prints the line where the syntax error
120 occurred with a caret on the next line indicating the approximate
121 position of the error.
122 """
123 value, tb = _parse_value_tb(exc, value, tb)
124 te = TracebackException(type(value), value, tb, limit=limit, compact=True)
125 te.print(file=file, chain=chain)
126
127
128 def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
129 chain=True):
130 """Format a stack trace and the exception information.
131
132 The arguments have the same meaning as the corresponding arguments
133 to print_exception(). The return value is a list of strings, each
134 ending in a newline and some containing internal newlines. When
135 these lines are concatenated and printed, exactly the same text is
136 printed as does print_exception().
137 """
138 value, tb = _parse_value_tb(exc, value, tb)
139 te = TracebackException(type(value), value, tb, limit=limit, compact=True)
140 return list(te.format(chain=chain))
141
142
143 def format_exception_only(exc, /, value=_sentinel):
144 """Format the exception part of a traceback.
145
146 The return value is a list of strings, each ending in a newline.
147
148 Normally, the list contains a single string; however, for
149 SyntaxError exceptions, it contains several lines that (when
150 printed) display detailed information about where the syntax
151 error occurred.
152
153 The message indicating which exception occurred is always the last
154 string in the list.
155
156 """
157 if value is _sentinel:
158 value = exc
159 te = TracebackException(type(value), value, None, compact=True)
160 return list(te.format_exception_only())
161
162
163 # -- not official API but folk probably use these two functions.
164
165 def _format_final_exc_line(etype, value):
166 valuestr = _safe_string(value, 'exception')
167 if value is None or not valuestr:
168 line = "%s\n" % etype
169 else:
170 line = "%s: %s\n" % (etype, valuestr)
171 return line
172
173 def _safe_string(value, what, func=str):
174 try:
175 return func(value)
176 except:
177 return f'<{what} {func.__name__}() failed>'
178
179 # --
180
181 def print_exc(limit=None, file=None, chain=True):
182 """Shorthand for 'print_exception(sys.exception(), limit, file, chain)'."""
183 print_exception(sys.exception(), limit=limit, file=file, chain=chain)
184
185 def format_exc(limit=None, chain=True):
186 """Like print_exc() but return a string."""
187 return "".join(format_exception(sys.exception(), limit=limit, chain=chain))
188
189 def print_last(limit=None, file=None, chain=True):
190 """This is a shorthand for 'print_exception(sys.last_exc, limit, file, chain)'."""
191 if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"):
192 raise ValueError("no last exception")
193
194 if hasattr(sys, "last_exc"):
195 print_exception(sys.last_exc, limit, file, chain)
196 else:
197 print_exception(sys.last_type, sys.last_value, sys.last_traceback,
198 limit, file, chain)
199
200
201 #
202 # Printing and Extracting Stacks.
203 #
204
205 def print_stack(f=None, limit=None, file=None):
206 """Print a stack trace from its invocation point.
207
208 The optional 'f' argument can be used to specify an alternate
209 stack frame at which to start. The optional 'limit' and 'file'
210 arguments have the same meaning as for print_exception().
211 """
212 if f is None:
213 f = sys._getframe().f_back
214 print_list(extract_stack(f, limit=limit), file=file)
215
216
217 def format_stack(f=None, limit=None):
218 """Shorthand for 'format_list(extract_stack(f, limit))'."""
219 if f is None:
220 f = sys._getframe().f_back
221 return format_list(extract_stack(f, limit=limit))
222
223
224 def extract_stack(f=None, limit=None):
225 """Extract the raw traceback from the current stack frame.
226
227 The return value has the same format as for extract_tb(). The
228 optional 'f' and 'limit' arguments have the same meaning as for
229 print_stack(). Each item in the list is a quadruple (filename,
230 line number, function name, text), and the entries are in order
231 from oldest to newest stack frame.
232 """
233 if f is None:
234 f = sys._getframe().f_back
235 stack = StackSummary.extract(walk_stack(f), limit=limit)
236 stack.reverse()
237 return stack
238
239
240 def clear_frames(tb):
241 "Clear all references to local variables in the frames of a traceback."
242 while tb is not None:
243 try:
244 tb.tb_frame.clear()
245 except RuntimeError:
246 # Ignore the exception raised if the frame is still executing.
247 pass
248 tb = tb.tb_next
249
250
251 class ESC[4;38;5;81mFrameSummary:
252 """Information about a single frame from a traceback.
253
254 - :attr:`filename` The filename for the frame.
255 - :attr:`lineno` The line within filename for the frame that was
256 active when the frame was captured.
257 - :attr:`name` The name of the function or method that was executing
258 when the frame was captured.
259 - :attr:`line` The text from the linecache module for the
260 of code that was running when the frame was captured.
261 - :attr:`locals` Either None if locals were not supplied, or a dict
262 mapping the name to the repr() of the variable.
263 """
264
265 __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
266 'name', '_line', 'locals')
267
268 def __init__(self, filename, lineno, name, *, lookup_line=True,
269 locals=None, line=None,
270 end_lineno=None, colno=None, end_colno=None):
271 """Construct a FrameSummary.
272
273 :param lookup_line: If True, `linecache` is consulted for the source
274 code line. Otherwise, the line will be looked up when first needed.
275 :param locals: If supplied the frame locals, which will be captured as
276 object representations.
277 :param line: If provided, use this instead of looking up the line in
278 the linecache.
279 """
280 self.filename = filename
281 self.lineno = lineno
282 self.name = name
283 self._line = line
284 if lookup_line:
285 self.line
286 self.locals = {k: _safe_string(v, 'local', func=repr)
287 for k, v in locals.items()} if locals else None
288 self.end_lineno = end_lineno
289 self.colno = colno
290 self.end_colno = end_colno
291
292 def __eq__(self, other):
293 if isinstance(other, FrameSummary):
294 return (self.filename == other.filename and
295 self.lineno == other.lineno and
296 self.name == other.name and
297 self.locals == other.locals)
298 if isinstance(other, tuple):
299 return (self.filename, self.lineno, self.name, self.line) == other
300 return NotImplemented
301
302 def __getitem__(self, pos):
303 return (self.filename, self.lineno, self.name, self.line)[pos]
304
305 def __iter__(self):
306 return iter([self.filename, self.lineno, self.name, self.line])
307
308 def __repr__(self):
309 return "<FrameSummary file {filename}, line {lineno} in {name}>".format(
310 filename=self.filename, lineno=self.lineno, name=self.name)
311
312 def __len__(self):
313 return 4
314
315 @property
316 def _original_line(self):
317 # Returns the line as-is from the source, without modifying whitespace.
318 self.line
319 return self._line
320
321 @property
322 def line(self):
323 if self._line is None:
324 if self.lineno is None:
325 return None
326 self._line = linecache.getline(self.filename, self.lineno)
327 return self._line.strip()
328
329
330 def walk_stack(f):
331 """Walk a stack yielding the frame and line number for each frame.
332
333 This will follow f.f_back from the given frame. If no frame is given, the
334 current stack is used. Usually used with StackSummary.extract.
335 """
336 if f is None:
337 f = sys._getframe().f_back.f_back.f_back.f_back
338 while f is not None:
339 yield f, f.f_lineno
340 f = f.f_back
341
342
343 def walk_tb(tb):
344 """Walk a traceback yielding the frame and line number for each frame.
345
346 This will follow tb.tb_next (and thus is in the opposite order to
347 walk_stack). Usually used with StackSummary.extract.
348 """
349 while tb is not None:
350 yield tb.tb_frame, tb.tb_lineno
351 tb = tb.tb_next
352
353
354 def _walk_tb_with_full_positions(tb):
355 # Internal version of walk_tb that yields full code positions including
356 # end line and column information.
357 while tb is not None:
358 positions = _get_code_position(tb.tb_frame.f_code, tb.tb_lasti)
359 # Yield tb_lineno when co_positions does not have a line number to
360 # maintain behavior with walk_tb.
361 if positions[0] is None:
362 yield tb.tb_frame, (tb.tb_lineno, ) + positions[1:]
363 else:
364 yield tb.tb_frame, positions
365 tb = tb.tb_next
366
367
368 def _get_code_position(code, instruction_index):
369 if instruction_index < 0:
370 return (None, None, None, None)
371 positions_gen = code.co_positions()
372 return next(itertools.islice(positions_gen, instruction_index // 2, None))
373
374
375 _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c.
376
377 class ESC[4;38;5;81mStackSummary(ESC[4;38;5;149mlist):
378 """A list of FrameSummary objects, representing a stack of frames."""
379
380 @classmethod
381 def extract(klass, frame_gen, *, limit=None, lookup_lines=True,
382 capture_locals=False):
383 """Create a StackSummary from a traceback or stack object.
384
385 :param frame_gen: A generator that yields (frame, lineno) tuples
386 whose summaries are to be included in the stack.
387 :param limit: None to include all frames or the number of frames to
388 include.
389 :param lookup_lines: If True, lookup lines for each frame immediately,
390 otherwise lookup is deferred until the frame is rendered.
391 :param capture_locals: If True, the local variables from each frame will
392 be captured as object representations into the FrameSummary.
393 """
394 def extended_frame_gen():
395 for f, lineno in frame_gen:
396 yield f, (lineno, None, None, None)
397
398 return klass._extract_from_extended_frame_gen(
399 extended_frame_gen(), limit=limit, lookup_lines=lookup_lines,
400 capture_locals=capture_locals)
401
402 @classmethod
403 def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None,
404 lookup_lines=True, capture_locals=False):
405 # Same as extract but operates on a frame generator that yields
406 # (frame, (lineno, end_lineno, colno, end_colno)) in the stack.
407 # Only lineno is required, the remaining fields can be None if the
408 # information is not available.
409 if limit is None:
410 limit = getattr(sys, 'tracebacklimit', None)
411 if limit is not None and limit < 0:
412 limit = 0
413 if limit is not None:
414 if limit >= 0:
415 frame_gen = itertools.islice(frame_gen, limit)
416 else:
417 frame_gen = collections.deque(frame_gen, maxlen=-limit)
418
419 result = klass()
420 fnames = set()
421 for f, (lineno, end_lineno, colno, end_colno) in frame_gen:
422 co = f.f_code
423 filename = co.co_filename
424 name = co.co_name
425
426 fnames.add(filename)
427 linecache.lazycache(filename, f.f_globals)
428 # Must defer line lookups until we have called checkcache.
429 if capture_locals:
430 f_locals = f.f_locals
431 else:
432 f_locals = None
433 result.append(FrameSummary(
434 filename, lineno, name, lookup_line=False, locals=f_locals,
435 end_lineno=end_lineno, colno=colno, end_colno=end_colno))
436 for filename in fnames:
437 linecache.checkcache(filename)
438 # If immediate lookup was desired, trigger lookups now.
439 if lookup_lines:
440 for f in result:
441 f.line
442 return result
443
444 @classmethod
445 def from_list(klass, a_list):
446 """
447 Create a StackSummary object from a supplied list of
448 FrameSummary objects or old-style list of tuples.
449 """
450 # While doing a fast-path check for isinstance(a_list, StackSummary) is
451 # appealing, idlelib.run.cleanup_traceback and other similar code may
452 # break this by making arbitrary frames plain tuples, so we need to
453 # check on a frame by frame basis.
454 result = StackSummary()
455 for frame in a_list:
456 if isinstance(frame, FrameSummary):
457 result.append(frame)
458 else:
459 filename, lineno, name, line = frame
460 result.append(FrameSummary(filename, lineno, name, line=line))
461 return result
462
463 def format_frame_summary(self, frame_summary):
464 """Format the lines for a single FrameSummary.
465
466 Returns a string representing one frame involved in the stack. This
467 gets called for every frame to be printed in the stack summary.
468 """
469 row = []
470 row.append(' File "{}", line {}, in {}\n'.format(
471 frame_summary.filename, frame_summary.lineno, frame_summary.name))
472 if frame_summary.line:
473 stripped_line = frame_summary.line.strip()
474 row.append(' {}\n'.format(stripped_line))
475
476 orig_line_len = len(frame_summary._original_line)
477 frame_line_len = len(frame_summary.line.lstrip())
478 stripped_characters = orig_line_len - frame_line_len
479 if (
480 frame_summary.colno is not None
481 and frame_summary.end_colno is not None
482 ):
483 start_offset = _byte_offset_to_character_offset(
484 frame_summary._original_line, frame_summary.colno) + 1
485 end_offset = _byte_offset_to_character_offset(
486 frame_summary._original_line, frame_summary.end_colno) + 1
487
488 anchors = None
489 if frame_summary.lineno == frame_summary.end_lineno:
490 with suppress(Exception):
491 anchors = _extract_caret_anchors_from_line_segment(
492 frame_summary._original_line[start_offset - 1:end_offset - 1]
493 )
494 else:
495 end_offset = stripped_characters + len(stripped_line)
496
497 # show indicators if primary char doesn't span the frame line
498 if end_offset - start_offset < len(stripped_line) or (
499 anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
500 row.append(' ')
501 row.append(' ' * (start_offset - stripped_characters))
502
503 if anchors:
504 row.append(anchors.primary_char * (anchors.left_end_offset))
505 row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
506 row.append(anchors.primary_char * (end_offset - start_offset - anchors.right_start_offset))
507 else:
508 row.append('^' * (end_offset - start_offset))
509
510 row.append('\n')
511
512 if frame_summary.locals:
513 for name, value in sorted(frame_summary.locals.items()):
514 row.append(' {name} = {value}\n'.format(name=name, value=value))
515
516 return ''.join(row)
517
518 def format(self):
519 """Format the stack ready for printing.
520
521 Returns a list of strings ready for printing. Each string in the
522 resulting list corresponds to a single frame from the stack.
523 Each string ends in a newline; the strings may contain internal
524 newlines as well, for those items with source text lines.
525
526 For long sequences of the same frame and line, the first few
527 repetitions are shown, followed by a summary line stating the exact
528 number of further repetitions.
529 """
530 result = []
531 last_file = None
532 last_line = None
533 last_name = None
534 count = 0
535 for frame_summary in self:
536 formatted_frame = self.format_frame_summary(frame_summary)
537 if formatted_frame is None:
538 continue
539 if (last_file is None or last_file != frame_summary.filename or
540 last_line is None or last_line != frame_summary.lineno or
541 last_name is None or last_name != frame_summary.name):
542 if count > _RECURSIVE_CUTOFF:
543 count -= _RECURSIVE_CUTOFF
544 result.append(
545 f' [Previous line repeated {count} more '
546 f'time{"s" if count > 1 else ""}]\n'
547 )
548 last_file = frame_summary.filename
549 last_line = frame_summary.lineno
550 last_name = frame_summary.name
551 count = 0
552 count += 1
553 if count > _RECURSIVE_CUTOFF:
554 continue
555 result.append(formatted_frame)
556
557 if count > _RECURSIVE_CUTOFF:
558 count -= _RECURSIVE_CUTOFF
559 result.append(
560 f' [Previous line repeated {count} more '
561 f'time{"s" if count > 1 else ""}]\n'
562 )
563 return result
564
565
566 def _byte_offset_to_character_offset(str, offset):
567 as_utf8 = str.encode('utf-8')
568 return len(as_utf8[:offset].decode("utf-8", errors="replace"))
569
570
571 _Anchors = collections.namedtuple(
572 "_Anchors",
573 [
574 "left_end_offset",
575 "right_start_offset",
576 "primary_char",
577 "secondary_char",
578 ],
579 defaults=["~", "^"]
580 )
581
582 def _extract_caret_anchors_from_line_segment(segment):
583 import ast
584
585 try:
586 tree = ast.parse(segment)
587 except SyntaxError:
588 return None
589
590 if len(tree.body) != 1:
591 return None
592
593 normalize = lambda offset: _byte_offset_to_character_offset(segment, offset)
594 statement = tree.body[0]
595 match statement:
596 case ast.Expr(expr):
597 match expr:
598 case ast.BinOp():
599 operator_start = normalize(expr.left.end_col_offset)
600 operator_end = normalize(expr.right.col_offset)
601 operator_str = segment[operator_start:operator_end]
602 operator_offset = len(operator_str) - len(operator_str.lstrip())
603
604 left_anchor = expr.left.end_col_offset + operator_offset
605 right_anchor = left_anchor + 1
606 if (
607 operator_offset + 1 < len(operator_str)
608 and not operator_str[operator_offset + 1].isspace()
609 ):
610 right_anchor += 1
611
612 while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
613 left_anchor += 1
614 right_anchor += 1
615 return _Anchors(normalize(left_anchor), normalize(right_anchor))
616 case ast.Subscript():
617 left_anchor = normalize(expr.value.end_col_offset)
618 right_anchor = normalize(expr.slice.end_col_offset + 1)
619 while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
620 left_anchor += 1
621 while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
622 right_anchor += 1
623 if right_anchor < len(segment):
624 right_anchor += 1
625 return _Anchors(left_anchor, right_anchor)
626
627 return None
628
629
630 class ESC[4;38;5;81m_ExceptionPrintContext:
631 def __init__(self):
632 self.seen = set()
633 self.exception_group_depth = 0
634 self.need_close = False
635
636 def indent(self):
637 return ' ' * (2 * self.exception_group_depth)
638
639 def emit(self, text_gen, margin_char=None):
640 if margin_char is None:
641 margin_char = '|'
642 indent_str = self.indent()
643 if self.exception_group_depth:
644 indent_str += margin_char + ' '
645
646 if isinstance(text_gen, str):
647 yield textwrap.indent(text_gen, indent_str, lambda line: True)
648 else:
649 for text in text_gen:
650 yield textwrap.indent(text, indent_str, lambda line: True)
651
652
653 class ESC[4;38;5;81mTracebackException:
654 """An exception ready for rendering.
655
656 The traceback module captures enough attributes from the original exception
657 to this intermediary form to ensure that no references are held, while
658 still being able to fully print or format it.
659
660 max_group_width and max_group_depth control the formatting of exception
661 groups. The depth refers to the nesting level of the group, and the width
662 refers to the size of a single exception group's exceptions array. The
663 formatted output is truncated when either limit is exceeded.
664
665 Use `from_exception` to create TracebackException instances from exception
666 objects, or the constructor to create TracebackException instances from
667 individual components.
668
669 - :attr:`__cause__` A TracebackException of the original *__cause__*.
670 - :attr:`__context__` A TracebackException of the original *__context__*.
671 - :attr:`exceptions` For exception groups - a list of TracebackException
672 instances for the nested *exceptions*. ``None`` for other exceptions.
673 - :attr:`__suppress_context__` The *__suppress_context__* value from the
674 original exception.
675 - :attr:`stack` A `StackSummary` representing the traceback.
676 - :attr:`exc_type` The class of the original traceback.
677 - :attr:`filename` For syntax errors - the filename where the error
678 occurred.
679 - :attr:`lineno` For syntax errors - the linenumber where the error
680 occurred.
681 - :attr:`end_lineno` For syntax errors - the end linenumber where the error
682 occurred. Can be `None` if not present.
683 - :attr:`text` For syntax errors - the text where the error
684 occurred.
685 - :attr:`offset` For syntax errors - the offset into the text where the
686 error occurred.
687 - :attr:`end_offset` For syntax errors - the end offset into the text where
688 the error occurred. Can be `None` if not present.
689 - :attr:`msg` For syntax errors - the compiler error message.
690 """
691
692 def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
693 lookup_lines=True, capture_locals=False, compact=False,
694 max_group_width=15, max_group_depth=10, _seen=None):
695 # NB: we need to accept exc_traceback, exc_value, exc_traceback to
696 # permit backwards compat with the existing API, otherwise we
697 # need stub thunk objects just to glue it together.
698 # Handle loops in __cause__ or __context__.
699 is_recursive_call = _seen is not None
700 if _seen is None:
701 _seen = set()
702 _seen.add(id(exc_value))
703
704 self.max_group_width = max_group_width
705 self.max_group_depth = max_group_depth
706
707 self.stack = StackSummary._extract_from_extended_frame_gen(
708 _walk_tb_with_full_positions(exc_traceback),
709 limit=limit, lookup_lines=lookup_lines,
710 capture_locals=capture_locals)
711 self.exc_type = exc_type
712 # Capture now to permit freeing resources: only complication is in the
713 # unofficial API _format_final_exc_line
714 self._str = _safe_string(exc_value, 'exception')
715 self.__notes__ = getattr(exc_value, '__notes__', None)
716
717 if exc_type and issubclass(exc_type, SyntaxError):
718 # Handle SyntaxError's specially
719 self.filename = exc_value.filename
720 lno = exc_value.lineno
721 self.lineno = str(lno) if lno is not None else None
722 end_lno = exc_value.end_lineno
723 self.end_lineno = str(end_lno) if end_lno is not None else None
724 self.text = exc_value.text
725 self.offset = exc_value.offset
726 self.end_offset = exc_value.end_offset
727 self.msg = exc_value.msg
728 elif exc_type and issubclass(exc_type, ImportError) and \
729 getattr(exc_value, "name_from", None) is not None:
730 wrong_name = getattr(exc_value, "name_from", None)
731 suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
732 if suggestion:
733 self._str += f". Did you mean: '{suggestion}'?"
734 elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \
735 getattr(exc_value, "name", None) is not None:
736 wrong_name = getattr(exc_value, "name", None)
737 suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
738 if suggestion:
739 self._str += f". Did you mean: '{suggestion}'?"
740 if issubclass(exc_type, NameError):
741 wrong_name = getattr(exc_value, "name", None)
742 if wrong_name is not None and wrong_name in sys.stdlib_module_names:
743 if suggestion:
744 self._str += f" Or did you forget to import '{wrong_name}'"
745 else:
746 self._str += f". Did you forget to import '{wrong_name}'"
747 if lookup_lines:
748 self._load_lines()
749 self.__suppress_context__ = \
750 exc_value.__suppress_context__ if exc_value is not None else False
751
752 # Convert __cause__ and __context__ to `TracebackExceptions`s, use a
753 # queue to avoid recursion (only the top-level call gets _seen == None)
754 if not is_recursive_call:
755 queue = [(self, exc_value)]
756 while queue:
757 te, e = queue.pop()
758 if (e and e.__cause__ is not None
759 and id(e.__cause__) not in _seen):
760 cause = TracebackException(
761 type(e.__cause__),
762 e.__cause__,
763 e.__cause__.__traceback__,
764 limit=limit,
765 lookup_lines=lookup_lines,
766 capture_locals=capture_locals,
767 max_group_width=max_group_width,
768 max_group_depth=max_group_depth,
769 _seen=_seen)
770 else:
771 cause = None
772
773 if compact:
774 need_context = (cause is None and
775 e is not None and
776 not e.__suppress_context__)
777 else:
778 need_context = True
779 if (e and e.__context__ is not None
780 and need_context and id(e.__context__) not in _seen):
781 context = TracebackException(
782 type(e.__context__),
783 e.__context__,
784 e.__context__.__traceback__,
785 limit=limit,
786 lookup_lines=lookup_lines,
787 capture_locals=capture_locals,
788 max_group_width=max_group_width,
789 max_group_depth=max_group_depth,
790 _seen=_seen)
791 else:
792 context = None
793
794 if e and isinstance(e, BaseExceptionGroup):
795 exceptions = []
796 for exc in e.exceptions:
797 texc = TracebackException(
798 type(exc),
799 exc,
800 exc.__traceback__,
801 limit=limit,
802 lookup_lines=lookup_lines,
803 capture_locals=capture_locals,
804 max_group_width=max_group_width,
805 max_group_depth=max_group_depth,
806 _seen=_seen)
807 exceptions.append(texc)
808 else:
809 exceptions = None
810
811 te.__cause__ = cause
812 te.__context__ = context
813 te.exceptions = exceptions
814 if cause:
815 queue.append((te.__cause__, e.__cause__))
816 if context:
817 queue.append((te.__context__, e.__context__))
818 if exceptions:
819 queue.extend(zip(te.exceptions, e.exceptions))
820
821 @classmethod
822 def from_exception(cls, exc, *args, **kwargs):
823 """Create a TracebackException from an exception."""
824 return cls(type(exc), exc, exc.__traceback__, *args, **kwargs)
825
826 def _load_lines(self):
827 """Private API. force all lines in the stack to be loaded."""
828 for frame in self.stack:
829 frame.line
830
831 def __eq__(self, other):
832 if isinstance(other, TracebackException):
833 return self.__dict__ == other.__dict__
834 return NotImplemented
835
836 def __str__(self):
837 return self._str
838
839 def format_exception_only(self):
840 """Format the exception part of the traceback.
841
842 The return value is a generator of strings, each ending in a newline.
843
844 Normally, the generator emits a single string; however, for
845 SyntaxError exceptions, it emits several lines that (when
846 printed) display detailed information about where the syntax
847 error occurred.
848
849 The message indicating which exception occurred is always the last
850 string in the output.
851 """
852 if self.exc_type is None:
853 yield _format_final_exc_line(None, self._str)
854 return
855
856 stype = self.exc_type.__qualname__
857 smod = self.exc_type.__module__
858 if smod not in ("__main__", "builtins"):
859 if not isinstance(smod, str):
860 smod = "<unknown>"
861 stype = smod + '.' + stype
862
863 if not issubclass(self.exc_type, SyntaxError):
864 yield _format_final_exc_line(stype, self._str)
865 else:
866 yield from self._format_syntax_error(stype)
867
868 if (
869 isinstance(self.__notes__, collections.abc.Sequence)
870 and not isinstance(self.__notes__, (str, bytes))
871 ):
872 for note in self.__notes__:
873 note = _safe_string(note, 'note')
874 yield from [l + '\n' for l in note.split('\n')]
875 elif self.__notes__ is not None:
876 yield "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr))
877
878 def _format_syntax_error(self, stype):
879 """Format SyntaxError exceptions (internal helper)."""
880 # Show exactly where the problem was found.
881 filename_suffix = ''
882 if self.lineno is not None:
883 yield ' File "{}", line {}\n'.format(
884 self.filename or "<string>", self.lineno)
885 elif self.filename is not None:
886 filename_suffix = ' ({})'.format(self.filename)
887
888 text = self.text
889 if text is not None:
890 # text = " foo\n"
891 # rtext = " foo"
892 # ltext = "foo"
893 rtext = text.rstrip('\n')
894 ltext = rtext.lstrip(' \n\f')
895 spaces = len(rtext) - len(ltext)
896 yield ' {}\n'.format(ltext)
897
898 if self.offset is not None:
899 offset = self.offset
900 end_offset = self.end_offset if self.end_offset not in {None, 0} else offset
901 if offset == end_offset or end_offset == -1:
902 end_offset = offset + 1
903
904 # Convert 1-based column offset to 0-based index into stripped text
905 colno = offset - 1 - spaces
906 end_colno = end_offset - 1 - spaces
907 if colno >= 0:
908 # non-space whitespace (likes tabs) must be kept for alignment
909 caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno])
910 yield ' {}{}'.format("".join(caretspace), ('^' * (end_colno - colno) + "\n"))
911 msg = self.msg or "<no detail available>"
912 yield "{}: {}{}\n".format(stype, msg, filename_suffix)
913
914 def format(self, *, chain=True, _ctx=None):
915 """Format the exception.
916
917 If chain is not *True*, *__cause__* and *__context__* will not be formatted.
918
919 The return value is a generator of strings, each ending in a newline and
920 some containing internal newlines. `print_exception` is a wrapper around
921 this method which just prints the lines to a file.
922
923 The message indicating which exception occurred is always the last
924 string in the output.
925 """
926
927 if _ctx is None:
928 _ctx = _ExceptionPrintContext()
929
930 output = []
931 exc = self
932 if chain:
933 while exc:
934 if exc.__cause__ is not None:
935 chained_msg = _cause_message
936 chained_exc = exc.__cause__
937 elif (exc.__context__ is not None and
938 not exc.__suppress_context__):
939 chained_msg = _context_message
940 chained_exc = exc.__context__
941 else:
942 chained_msg = None
943 chained_exc = None
944
945 output.append((chained_msg, exc))
946 exc = chained_exc
947 else:
948 output.append((None, exc))
949
950 for msg, exc in reversed(output):
951 if msg is not None:
952 yield from _ctx.emit(msg)
953 if exc.exceptions is None:
954 if exc.stack:
955 yield from _ctx.emit('Traceback (most recent call last):\n')
956 yield from _ctx.emit(exc.stack.format())
957 yield from _ctx.emit(exc.format_exception_only())
958 elif _ctx.exception_group_depth > self.max_group_depth:
959 # exception group, but depth exceeds limit
960 yield from _ctx.emit(
961 f"... (max_group_depth is {self.max_group_depth})\n")
962 else:
963 # format exception group
964 is_toplevel = (_ctx.exception_group_depth == 0)
965 if is_toplevel:
966 _ctx.exception_group_depth += 1
967
968 if exc.stack:
969 yield from _ctx.emit(
970 'Exception Group Traceback (most recent call last):\n',
971 margin_char = '+' if is_toplevel else None)
972 yield from _ctx.emit(exc.stack.format())
973
974 yield from _ctx.emit(exc.format_exception_only())
975 num_excs = len(exc.exceptions)
976 if num_excs <= self.max_group_width:
977 n = num_excs
978 else:
979 n = self.max_group_width + 1
980 _ctx.need_close = False
981 for i in range(n):
982 last_exc = (i == n-1)
983 if last_exc:
984 # The closing frame may be added by a recursive call
985 _ctx.need_close = True
986
987 if self.max_group_width is not None:
988 truncated = (i >= self.max_group_width)
989 else:
990 truncated = False
991 title = f'{i+1}' if not truncated else '...'
992 yield (_ctx.indent() +
993 ('+-' if i==0 else ' ') +
994 f'+---------------- {title} ----------------\n')
995 _ctx.exception_group_depth += 1
996 if not truncated:
997 yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx)
998 else:
999 remaining = num_excs - self.max_group_width
1000 plural = 's' if remaining > 1 else ''
1001 yield from _ctx.emit(
1002 f"and {remaining} more exception{plural}\n")
1003
1004 if last_exc and _ctx.need_close:
1005 yield (_ctx.indent() +
1006 "+------------------------------------\n")
1007 _ctx.need_close = False
1008 _ctx.exception_group_depth -= 1
1009
1010 if is_toplevel:
1011 assert _ctx.exception_group_depth == 1
1012 _ctx.exception_group_depth = 0
1013
1014
1015 def print(self, *, file=None, chain=True):
1016 """Print the result of self.format(chain=chain) to 'file'."""
1017 if file is None:
1018 file = sys.stderr
1019 for line in self.format(chain=chain):
1020 print(line, file=file, end="")
1021
1022
1023 _MAX_CANDIDATE_ITEMS = 750
1024 _MAX_STRING_SIZE = 40
1025 _MOVE_COST = 2
1026 _CASE_COST = 1
1027
1028
1029 def _substitution_cost(ch_a, ch_b):
1030 if ch_a == ch_b:
1031 return 0
1032 if ch_a.lower() == ch_b.lower():
1033 return _CASE_COST
1034 return _MOVE_COST
1035
1036
1037 def _compute_suggestion_error(exc_value, tb, wrong_name):
1038 if wrong_name is None or not isinstance(wrong_name, str):
1039 return None
1040 if isinstance(exc_value, AttributeError):
1041 obj = exc_value.obj
1042 try:
1043 d = dir(obj)
1044 except Exception:
1045 return None
1046 elif isinstance(exc_value, ImportError):
1047 try:
1048 mod = __import__(exc_value.name)
1049 d = dir(mod)
1050 except Exception:
1051 return None
1052 else:
1053 assert isinstance(exc_value, NameError)
1054 # find most recent frame
1055 if tb is None:
1056 return None
1057 while tb.tb_next is not None:
1058 tb = tb.tb_next
1059 frame = tb.tb_frame
1060 d = (
1061 list(frame.f_locals)
1062 + list(frame.f_globals)
1063 + list(frame.f_builtins)
1064 )
1065
1066 # Check first if we are in a method and the instance
1067 # has the wrong name as attribute
1068 if 'self' in frame.f_locals:
1069 self = frame.f_locals['self']
1070 if hasattr(self, wrong_name):
1071 return f"self.{wrong_name}"
1072
1073 # Compute closest match
1074
1075 if len(d) > _MAX_CANDIDATE_ITEMS:
1076 return None
1077 wrong_name_len = len(wrong_name)
1078 if wrong_name_len > _MAX_STRING_SIZE:
1079 return None
1080 best_distance = wrong_name_len
1081 suggestion = None
1082 for possible_name in d:
1083 if possible_name == wrong_name:
1084 # A missing attribute is "found". Don't suggest it (see GH-88821).
1085 continue
1086 # No more than 1/3 of the involved characters should need changed.
1087 max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
1088 # Don't take matches we've already beaten.
1089 max_distance = min(max_distance, best_distance - 1)
1090 current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance)
1091 if current_distance > max_distance:
1092 continue
1093 if not suggestion or current_distance < best_distance:
1094 suggestion = possible_name
1095 best_distance = current_distance
1096 return suggestion
1097
1098
1099 def _levenshtein_distance(a, b, max_cost):
1100 # A Python implementation of Python/suggestions.c:levenshtein_distance.
1101
1102 # Both strings are the same
1103 if a == b:
1104 return 0
1105
1106 # Trim away common affixes
1107 pre = 0
1108 while a[pre:] and b[pre:] and a[pre] == b[pre]:
1109 pre += 1
1110 a = a[pre:]
1111 b = b[pre:]
1112 post = 0
1113 while a[:post or None] and b[:post or None] and a[post-1] == b[post-1]:
1114 post -= 1
1115 a = a[:post or None]
1116 b = b[:post or None]
1117 if not a or not b:
1118 return _MOVE_COST * (len(a) + len(b))
1119 if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE:
1120 return max_cost + 1
1121
1122 # Prefer shorter buffer
1123 if len(b) < len(a):
1124 a, b = b, a
1125
1126 # Quick fail when a match is impossible
1127 if (len(b) - len(a)) * _MOVE_COST > max_cost:
1128 return max_cost + 1
1129
1130 # Instead of producing the whole traditional len(a)-by-len(b)
1131 # matrix, we can update just one row in place.
1132 # Initialize the buffer row
1133 row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST))
1134
1135 result = 0
1136 for bindex in range(len(b)):
1137 bchar = b[bindex]
1138 distance = result = bindex * _MOVE_COST
1139 minimum = sys.maxsize
1140 for index in range(len(a)):
1141 # 1) Previous distance in this row is cost(b[:b_index], a[:index])
1142 substitute = distance + _substitution_cost(bchar, a[index])
1143 # 2) cost(b[:b_index], a[:index+1]) from previous row
1144 distance = row[index]
1145 # 3) existing result is cost(b[:b_index+1], a[index])
1146
1147 insert_delete = min(result, distance) + _MOVE_COST
1148 result = min(insert_delete, substitute)
1149
1150 # cost(b[:b_index+1], a[:index+1])
1151 row[index] = result
1152 if result < minimum:
1153 minimum = result
1154 if minimum > max_cost:
1155 # Everything in this row is too big, so bail early.
1156 return max_cost + 1
1157 return result