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 The list contains the exception's message, which is
149 normally a single string; however, for :exc:`SyntaxError` exceptions, it
150 contains several lines that (when printed) display detailed information
151 about where the syntax error occurred. Following the message, the list
152 contains the exception's ``__notes__``.
153 """
154 if value is _sentinel:
155 value = exc
156 te = TracebackException(type(value), value, None, compact=True)
157 return list(te.format_exception_only())
158
159
160 # -- not official API but folk probably use these two functions.
161
162 def _format_final_exc_line(etype, value):
163 valuestr = _safe_string(value, 'exception')
164 if value is None or not valuestr:
165 line = "%s\n" % etype
166 else:
167 line = "%s: %s\n" % (etype, valuestr)
168 return line
169
170 def _safe_string(value, what, func=str):
171 try:
172 return func(value)
173 except:
174 return f'<{what} {func.__name__}() failed>'
175
176 # --
177
178 def print_exc(limit=None, file=None, chain=True):
179 """Shorthand for 'print_exception(*sys.exc_info(), limit, file)'."""
180 print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain)
181
182 def format_exc(limit=None, chain=True):
183 """Like print_exc() but return a string."""
184 return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
185
186 def print_last(limit=None, file=None, chain=True):
187 """This is a shorthand for 'print_exception(sys.last_type,
188 sys.last_value, sys.last_traceback, limit, file)'."""
189 if not hasattr(sys, "last_type"):
190 raise ValueError("no last exception")
191 print_exception(sys.last_type, sys.last_value, sys.last_traceback,
192 limit, file, chain)
193
194 #
195 # Printing and Extracting Stacks.
196 #
197
198 def print_stack(f=None, limit=None, file=None):
199 """Print a stack trace from its invocation point.
200
201 The optional 'f' argument can be used to specify an alternate
202 stack frame at which to start. The optional 'limit' and 'file'
203 arguments have the same meaning as for print_exception().
204 """
205 if f is None:
206 f = sys._getframe().f_back
207 print_list(extract_stack(f, limit=limit), file=file)
208
209
210 def format_stack(f=None, limit=None):
211 """Shorthand for 'format_list(extract_stack(f, limit))'."""
212 if f is None:
213 f = sys._getframe().f_back
214 return format_list(extract_stack(f, limit=limit))
215
216
217 def extract_stack(f=None, limit=None):
218 """Extract the raw traceback from the current stack frame.
219
220 The return value has the same format as for extract_tb(). The
221 optional 'f' and 'limit' arguments have the same meaning as for
222 print_stack(). Each item in the list is a quadruple (filename,
223 line number, function name, text), and the entries are in order
224 from oldest to newest stack frame.
225 """
226 if f is None:
227 f = sys._getframe().f_back
228 stack = StackSummary.extract(walk_stack(f), limit=limit)
229 stack.reverse()
230 return stack
231
232
233 def clear_frames(tb):
234 "Clear all references to local variables in the frames of a traceback."
235 while tb is not None:
236 try:
237 tb.tb_frame.clear()
238 except RuntimeError:
239 # Ignore the exception raised if the frame is still executing.
240 pass
241 tb = tb.tb_next
242
243
244 class ESC[4;38;5;81mFrameSummary:
245 """Information about a single frame from a traceback.
246
247 - :attr:`filename` The filename for the frame.
248 - :attr:`lineno` The line within filename for the frame that was
249 active when the frame was captured.
250 - :attr:`name` The name of the function or method that was executing
251 when the frame was captured.
252 - :attr:`line` The text from the linecache module for the
253 of code that was running when the frame was captured.
254 - :attr:`locals` Either None if locals were not supplied, or a dict
255 mapping the name to the repr() of the variable.
256 """
257
258 __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
259 'name', '_line', 'locals')
260
261 def __init__(self, filename, lineno, name, *, lookup_line=True,
262 locals=None, line=None,
263 end_lineno=None, colno=None, end_colno=None):
264 """Construct a FrameSummary.
265
266 :param lookup_line: If True, `linecache` is consulted for the source
267 code line. Otherwise, the line will be looked up when first needed.
268 :param locals: If supplied the frame locals, which will be captured as
269 object representations.
270 :param line: If provided, use this instead of looking up the line in
271 the linecache.
272 """
273 self.filename = filename
274 self.lineno = lineno
275 self.name = name
276 self._line = line
277 if lookup_line:
278 self.line
279 self.locals = {k: repr(v) for k, v in locals.items()} if locals else None
280 self.end_lineno = end_lineno
281 self.colno = colno
282 self.end_colno = end_colno
283
284 def __eq__(self, other):
285 if isinstance(other, FrameSummary):
286 return (self.filename == other.filename and
287 self.lineno == other.lineno and
288 self.name == other.name and
289 self.locals == other.locals)
290 if isinstance(other, tuple):
291 return (self.filename, self.lineno, self.name, self.line) == other
292 return NotImplemented
293
294 def __getitem__(self, pos):
295 return (self.filename, self.lineno, self.name, self.line)[pos]
296
297 def __iter__(self):
298 return iter([self.filename, self.lineno, self.name, self.line])
299
300 def __repr__(self):
301 return "<FrameSummary file {filename}, line {lineno} in {name}>".format(
302 filename=self.filename, lineno=self.lineno, name=self.name)
303
304 def __len__(self):
305 return 4
306
307 @property
308 def _original_line(self):
309 # Returns the line as-is from the source, without modifying whitespace.
310 self.line
311 return self._line
312
313 @property
314 def line(self):
315 if self._line is None:
316 if self.lineno is None:
317 return None
318 self._line = linecache.getline(self.filename, self.lineno)
319 return self._line.strip()
320
321
322 def walk_stack(f):
323 """Walk a stack yielding the frame and line number for each frame.
324
325 This will follow f.f_back from the given frame. If no frame is given, the
326 current stack is used. Usually used with StackSummary.extract.
327 """
328 if f is None:
329 f = sys._getframe().f_back.f_back.f_back.f_back
330 while f is not None:
331 yield f, f.f_lineno
332 f = f.f_back
333
334
335 def walk_tb(tb):
336 """Walk a traceback yielding the frame and line number for each frame.
337
338 This will follow tb.tb_next (and thus is in the opposite order to
339 walk_stack). Usually used with StackSummary.extract.
340 """
341 while tb is not None:
342 yield tb.tb_frame, tb.tb_lineno
343 tb = tb.tb_next
344
345
346 def _walk_tb_with_full_positions(tb):
347 # Internal version of walk_tb that yields full code positions including
348 # end line and column information.
349 while tb is not None:
350 positions = _get_code_position(tb.tb_frame.f_code, tb.tb_lasti)
351 # Yield tb_lineno when co_positions does not have a line number to
352 # maintain behavior with walk_tb.
353 if positions[0] is None:
354 yield tb.tb_frame, (tb.tb_lineno, ) + positions[1:]
355 else:
356 yield tb.tb_frame, positions
357 tb = tb.tb_next
358
359
360 def _get_code_position(code, instruction_index):
361 if instruction_index < 0:
362 return (None, None, None, None)
363 positions_gen = code.co_positions()
364 return next(itertools.islice(positions_gen, instruction_index // 2, None))
365
366
367 _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c.
368
369 class ESC[4;38;5;81mStackSummary(ESC[4;38;5;149mlist):
370 """A list of FrameSummary objects, representing a stack of frames."""
371
372 @classmethod
373 def extract(klass, frame_gen, *, limit=None, lookup_lines=True,
374 capture_locals=False):
375 """Create a StackSummary from a traceback or stack object.
376
377 :param frame_gen: A generator that yields (frame, lineno) tuples
378 whose summaries are to be included in the stack.
379 :param limit: None to include all frames or the number of frames to
380 include.
381 :param lookup_lines: If True, lookup lines for each frame immediately,
382 otherwise lookup is deferred until the frame is rendered.
383 :param capture_locals: If True, the local variables from each frame will
384 be captured as object representations into the FrameSummary.
385 """
386 def extended_frame_gen():
387 for f, lineno in frame_gen:
388 yield f, (lineno, None, None, None)
389
390 return klass._extract_from_extended_frame_gen(
391 extended_frame_gen(), limit=limit, lookup_lines=lookup_lines,
392 capture_locals=capture_locals)
393
394 @classmethod
395 def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None,
396 lookup_lines=True, capture_locals=False):
397 # Same as extract but operates on a frame generator that yields
398 # (frame, (lineno, end_lineno, colno, end_colno)) in the stack.
399 # Only lineno is required, the remaining fields can be None if the
400 # information is not available.
401 if limit is None:
402 limit = getattr(sys, 'tracebacklimit', None)
403 if limit is not None and limit < 0:
404 limit = 0
405 if limit is not None:
406 if limit >= 0:
407 frame_gen = itertools.islice(frame_gen, limit)
408 else:
409 frame_gen = collections.deque(frame_gen, maxlen=-limit)
410
411 result = klass()
412 fnames = set()
413 for f, (lineno, end_lineno, colno, end_colno) in frame_gen:
414 co = f.f_code
415 filename = co.co_filename
416 name = co.co_name
417
418 fnames.add(filename)
419 linecache.lazycache(filename, f.f_globals)
420 # Must defer line lookups until we have called checkcache.
421 if capture_locals:
422 f_locals = f.f_locals
423 else:
424 f_locals = None
425 result.append(FrameSummary(
426 filename, lineno, name, lookup_line=False, locals=f_locals,
427 end_lineno=end_lineno, colno=colno, end_colno=end_colno))
428 for filename in fnames:
429 linecache.checkcache(filename)
430 # If immediate lookup was desired, trigger lookups now.
431 if lookup_lines:
432 for f in result:
433 f.line
434 return result
435
436 @classmethod
437 def from_list(klass, a_list):
438 """
439 Create a StackSummary object from a supplied list of
440 FrameSummary objects or old-style list of tuples.
441 """
442 # While doing a fast-path check for isinstance(a_list, StackSummary) is
443 # appealing, idlelib.run.cleanup_traceback and other similar code may
444 # break this by making arbitrary frames plain tuples, so we need to
445 # check on a frame by frame basis.
446 result = StackSummary()
447 for frame in a_list:
448 if isinstance(frame, FrameSummary):
449 result.append(frame)
450 else:
451 filename, lineno, name, line = frame
452 result.append(FrameSummary(filename, lineno, name, line=line))
453 return result
454
455 def format_frame_summary(self, frame_summary):
456 """Format the lines for a single FrameSummary.
457
458 Returns a string representing one frame involved in the stack. This
459 gets called for every frame to be printed in the stack summary.
460 """
461 row = []
462 row.append(' File "{}", line {}, in {}\n'.format(
463 frame_summary.filename, frame_summary.lineno, frame_summary.name))
464 if frame_summary.line:
465 stripped_line = frame_summary.line.strip()
466 row.append(' {}\n'.format(stripped_line))
467
468 line = frame_summary._original_line
469 orig_line_len = len(line)
470 frame_line_len = len(frame_summary.line.lstrip())
471 stripped_characters = orig_line_len - frame_line_len
472 if (
473 frame_summary.colno is not None
474 and frame_summary.end_colno is not None
475 ):
476 start_offset = _byte_offset_to_character_offset(
477 line, frame_summary.colno)
478 end_offset = _byte_offset_to_character_offset(
479 line, frame_summary.end_colno)
480 code_segment = line[start_offset:end_offset]
481
482 anchors = None
483 if frame_summary.lineno == frame_summary.end_lineno:
484 with suppress(Exception):
485 anchors = _extract_caret_anchors_from_line_segment(code_segment)
486 else:
487 # Don't count the newline since the anchors only need to
488 # go up until the last character of the line.
489 end_offset = len(line.rstrip())
490
491 # show indicators if primary char doesn't span the frame line
492 if end_offset - start_offset < len(stripped_line) or (
493 anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
494 # When showing this on a terminal, some of the non-ASCII characters
495 # might be rendered as double-width characters, so we need to take
496 # that into account when calculating the length of the line.
497 dp_start_offset = _display_width(line, start_offset) + 1
498 dp_end_offset = _display_width(line, end_offset) + 1
499
500 row.append(' ')
501 row.append(' ' * (dp_start_offset - stripped_characters))
502
503 if anchors:
504 dp_left_end_offset = _display_width(code_segment, anchors.left_end_offset)
505 dp_right_start_offset = _display_width(code_segment, anchors.right_start_offset)
506 row.append(anchors.primary_char * dp_left_end_offset)
507 row.append(anchors.secondary_char * (dp_right_start_offset - dp_left_end_offset))
508 row.append(anchors.primary_char * (dp_end_offset - dp_start_offset - dp_right_start_offset))
509 else:
510 row.append('^' * (dp_end_offset - dp_start_offset))
511
512 row.append('\n')
513
514 if frame_summary.locals:
515 for name, value in sorted(frame_summary.locals.items()):
516 row.append(' {name} = {value}\n'.format(name=name, value=value))
517
518 return ''.join(row)
519
520 def format(self):
521 """Format the stack ready for printing.
522
523 Returns a list of strings ready for printing. Each string in the
524 resulting list corresponds to a single frame from the stack.
525 Each string ends in a newline; the strings may contain internal
526 newlines as well, for those items with source text lines.
527
528 For long sequences of the same frame and line, the first few
529 repetitions are shown, followed by a summary line stating the exact
530 number of further repetitions.
531 """
532 result = []
533 last_file = None
534 last_line = None
535 last_name = None
536 count = 0
537 for frame_summary in self:
538 formatted_frame = self.format_frame_summary(frame_summary)
539 if formatted_frame is None:
540 continue
541 if (last_file is None or last_file != frame_summary.filename or
542 last_line is None or last_line != frame_summary.lineno or
543 last_name is None or last_name != frame_summary.name):
544 if count > _RECURSIVE_CUTOFF:
545 count -= _RECURSIVE_CUTOFF
546 result.append(
547 f' [Previous line repeated {count} more '
548 f'time{"s" if count > 1 else ""}]\n'
549 )
550 last_file = frame_summary.filename
551 last_line = frame_summary.lineno
552 last_name = frame_summary.name
553 count = 0
554 count += 1
555 if count > _RECURSIVE_CUTOFF:
556 continue
557 result.append(formatted_frame)
558
559 if count > _RECURSIVE_CUTOFF:
560 count -= _RECURSIVE_CUTOFF
561 result.append(
562 f' [Previous line repeated {count} more '
563 f'time{"s" if count > 1 else ""}]\n'
564 )
565 return result
566
567
568 def _byte_offset_to_character_offset(str, offset):
569 as_utf8 = str.encode('utf-8')
570 return len(as_utf8[:offset].decode("utf-8", errors="replace"))
571
572
573 _Anchors = collections.namedtuple(
574 "_Anchors",
575 [
576 "left_end_offset",
577 "right_start_offset",
578 "primary_char",
579 "secondary_char",
580 ],
581 defaults=["~", "^"]
582 )
583
584 def _extract_caret_anchors_from_line_segment(segment):
585 import ast
586
587 try:
588 tree = ast.parse(segment)
589 except SyntaxError:
590 return None
591
592 if len(tree.body) != 1:
593 return None
594
595 normalize = lambda offset: _byte_offset_to_character_offset(segment, offset)
596 statement = tree.body[0]
597 match statement:
598 case ast.Expr(expr):
599 match expr:
600 case ast.BinOp():
601 operator_start = normalize(expr.left.end_col_offset)
602 operator_end = normalize(expr.right.col_offset)
603 operator_str = segment[operator_start:operator_end]
604 operator_offset = len(operator_str) - len(operator_str.lstrip())
605
606 left_anchor = expr.left.end_col_offset + operator_offset
607 right_anchor = left_anchor + 1
608 if (
609 operator_offset + 1 < len(operator_str)
610 and not operator_str[operator_offset + 1].isspace()
611 ):
612 right_anchor += 1
613
614 while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"):
615 left_anchor += 1
616 right_anchor += 1
617 return _Anchors(normalize(left_anchor), normalize(right_anchor))
618 case ast.Subscript():
619 left_anchor = normalize(expr.value.end_col_offset)
620 right_anchor = normalize(expr.slice.end_col_offset + 1)
621 while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["):
622 left_anchor += 1
623 while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"):
624 right_anchor += 1
625 if right_anchor < len(segment):
626 right_anchor += 1
627 return _Anchors(left_anchor, right_anchor)
628
629 return None
630
631 _WIDE_CHAR_SPECIFIERS = "WF"
632
633 def _display_width(line, offset):
634 """Calculate the extra amount of width space the given source
635 code segment might take if it were to be displayed on a fixed
636 width output device. Supports wide unicode characters and emojis."""
637
638 # Fast track for ASCII-only strings
639 if line.isascii():
640 return offset
641
642 import unicodedata
643
644 return sum(
645 2 if unicodedata.east_asian_width(char) in _WIDE_CHAR_SPECIFIERS else 1
646 for char in line[:offset]
647 )
648
649
650
651 class ESC[4;38;5;81m_ExceptionPrintContext:
652 def __init__(self):
653 self.seen = set()
654 self.exception_group_depth = 0
655 self.need_close = False
656
657 def indent(self):
658 return ' ' * (2 * self.exception_group_depth)
659
660 def emit(self, text_gen, margin_char=None):
661 if margin_char is None:
662 margin_char = '|'
663 indent_str = self.indent()
664 if self.exception_group_depth:
665 indent_str += margin_char + ' '
666
667 if isinstance(text_gen, str):
668 yield textwrap.indent(text_gen, indent_str, lambda line: True)
669 else:
670 for text in text_gen:
671 yield textwrap.indent(text, indent_str, lambda line: True)
672
673
674 class ESC[4;38;5;81mTracebackException:
675 """An exception ready for rendering.
676
677 The traceback module captures enough attributes from the original exception
678 to this intermediary form to ensure that no references are held, while
679 still being able to fully print or format it.
680
681 max_group_width and max_group_depth control the formatting of exception
682 groups. The depth refers to the nesting level of the group, and the width
683 refers to the size of a single exception group's exceptions array. The
684 formatted output is truncated when either limit is exceeded.
685
686 Use `from_exception` to create TracebackException instances from exception
687 objects, or the constructor to create TracebackException instances from
688 individual components.
689
690 - :attr:`__cause__` A TracebackException of the original *__cause__*.
691 - :attr:`__context__` A TracebackException of the original *__context__*.
692 - :attr:`exceptions` For exception groups - a list of TracebackException
693 instances for the nested *exceptions*. ``None`` for other exceptions.
694 - :attr:`__suppress_context__` The *__suppress_context__* value from the
695 original exception.
696 - :attr:`stack` A `StackSummary` representing the traceback.
697 - :attr:`exc_type` The class of the original traceback.
698 - :attr:`filename` For syntax errors - the filename where the error
699 occurred.
700 - :attr:`lineno` For syntax errors - the linenumber where the error
701 occurred.
702 - :attr:`end_lineno` For syntax errors - the end linenumber where the error
703 occurred. Can be `None` if not present.
704 - :attr:`text` For syntax errors - the text where the error
705 occurred.
706 - :attr:`offset` For syntax errors - the offset into the text where the
707 error occurred.
708 - :attr:`end_offset` For syntax errors - the end offset into the text where
709 the error occurred. Can be `None` if not present.
710 - :attr:`msg` For syntax errors - the compiler error message.
711 """
712
713 def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
714 lookup_lines=True, capture_locals=False, compact=False,
715 max_group_width=15, max_group_depth=10, _seen=None):
716 # NB: we need to accept exc_traceback, exc_value, exc_traceback to
717 # permit backwards compat with the existing API, otherwise we
718 # need stub thunk objects just to glue it together.
719 # Handle loops in __cause__ or __context__.
720 is_recursive_call = _seen is not None
721 if _seen is None:
722 _seen = set()
723 _seen.add(id(exc_value))
724
725 self.max_group_width = max_group_width
726 self.max_group_depth = max_group_depth
727
728 self.stack = StackSummary._extract_from_extended_frame_gen(
729 _walk_tb_with_full_positions(exc_traceback),
730 limit=limit, lookup_lines=lookup_lines,
731 capture_locals=capture_locals)
732 self.exc_type = exc_type
733 # Capture now to permit freeing resources: only complication is in the
734 # unofficial API _format_final_exc_line
735 self._str = _safe_string(exc_value, 'exception')
736 self.__notes__ = getattr(exc_value, '__notes__', None)
737
738 if exc_type and issubclass(exc_type, SyntaxError):
739 # Handle SyntaxError's specially
740 self.filename = exc_value.filename
741 lno = exc_value.lineno
742 self.lineno = str(lno) if lno is not None else None
743 end_lno = exc_value.end_lineno
744 self.end_lineno = str(end_lno) if end_lno is not None else None
745 self.text = exc_value.text
746 self.offset = exc_value.offset
747 self.end_offset = exc_value.end_offset
748 self.msg = exc_value.msg
749 if lookup_lines:
750 self._load_lines()
751 self.__suppress_context__ = \
752 exc_value.__suppress_context__ if exc_value is not None else False
753
754 # Convert __cause__ and __context__ to `TracebackExceptions`s, use a
755 # queue to avoid recursion (only the top-level call gets _seen == None)
756 if not is_recursive_call:
757 queue = [(self, exc_value)]
758 while queue:
759 te, e = queue.pop()
760 if (e and e.__cause__ is not None
761 and id(e.__cause__) not in _seen):
762 cause = TracebackException(
763 type(e.__cause__),
764 e.__cause__,
765 e.__cause__.__traceback__,
766 limit=limit,
767 lookup_lines=lookup_lines,
768 capture_locals=capture_locals,
769 max_group_width=max_group_width,
770 max_group_depth=max_group_depth,
771 _seen=_seen)
772 else:
773 cause = None
774
775 if compact:
776 need_context = (cause is None and
777 e is not None and
778 not e.__suppress_context__)
779 else:
780 need_context = True
781 if (e and e.__context__ is not None
782 and need_context and id(e.__context__) not in _seen):
783 context = TracebackException(
784 type(e.__context__),
785 e.__context__,
786 e.__context__.__traceback__,
787 limit=limit,
788 lookup_lines=lookup_lines,
789 capture_locals=capture_locals,
790 max_group_width=max_group_width,
791 max_group_depth=max_group_depth,
792 _seen=_seen)
793 else:
794 context = None
795
796 if e and isinstance(e, BaseExceptionGroup):
797 exceptions = []
798 for exc in e.exceptions:
799 texc = TracebackException(
800 type(exc),
801 exc,
802 exc.__traceback__,
803 limit=limit,
804 lookup_lines=lookup_lines,
805 capture_locals=capture_locals,
806 max_group_width=max_group_width,
807 max_group_depth=max_group_depth,
808 _seen=_seen)
809 exceptions.append(texc)
810 else:
811 exceptions = None
812
813 te.__cause__ = cause
814 te.__context__ = context
815 te.exceptions = exceptions
816 if cause:
817 queue.append((te.__cause__, e.__cause__))
818 if context:
819 queue.append((te.__context__, e.__context__))
820 if exceptions:
821 queue.extend(zip(te.exceptions, e.exceptions))
822
823 @classmethod
824 def from_exception(cls, exc, *args, **kwargs):
825 """Create a TracebackException from an exception."""
826 return cls(type(exc), exc, exc.__traceback__, *args, **kwargs)
827
828 def _load_lines(self):
829 """Private API. force all lines in the stack to be loaded."""
830 for frame in self.stack:
831 frame.line
832
833 def __eq__(self, other):
834 if isinstance(other, TracebackException):
835 return self.__dict__ == other.__dict__
836 return NotImplemented
837
838 def __str__(self):
839 return self._str
840
841 def format_exception_only(self):
842 """Format the exception part of the traceback.
843
844 The return value is a generator of strings, each ending in a newline.
845
846 Generator yields the exception message.
847 For :exc:`SyntaxError` exceptions, it
848 also yields (before the exception message)
849 several lines that (when printed)
850 display detailed information about where the syntax error occurred.
851 Following the message, generator also yields
852 all the exception's ``__notes__``.
853 """
854 if self.exc_type is None:
855 yield _format_final_exc_line(None, self._str)
856 return
857
858 stype = self.exc_type.__qualname__
859 smod = self.exc_type.__module__
860 if smod not in ("__main__", "builtins"):
861 if not isinstance(smod, str):
862 smod = "<unknown>"
863 stype = smod + '.' + stype
864
865 if not issubclass(self.exc_type, SyntaxError):
866 yield _format_final_exc_line(stype, self._str)
867 else:
868 yield from self._format_syntax_error(stype)
869 if isinstance(self.__notes__, collections.abc.Sequence):
870 for note in self.__notes__:
871 note = _safe_string(note, 'note')
872 yield from [l + '\n' for l in note.split('\n')]
873 elif self.__notes__ is not None:
874 yield _safe_string(self.__notes__, '__notes__', func=repr)
875
876 def _format_syntax_error(self, stype):
877 """Format SyntaxError exceptions (internal helper)."""
878 # Show exactly where the problem was found.
879 filename_suffix = ''
880 if self.lineno is not None:
881 yield ' File "{}", line {}\n'.format(
882 self.filename or "<string>", self.lineno)
883 elif self.filename is not None:
884 filename_suffix = ' ({})'.format(self.filename)
885
886 text = self.text
887 if text is not None:
888 # text = " foo\n"
889 # rtext = " foo"
890 # ltext = "foo"
891 rtext = text.rstrip('\n')
892 ltext = rtext.lstrip(' \n\f')
893 spaces = len(rtext) - len(ltext)
894 yield ' {}\n'.format(ltext)
895
896 if self.offset is not None:
897 offset = self.offset
898 end_offset = self.end_offset if self.end_offset not in {None, 0} else offset
899 if offset == end_offset or end_offset == -1:
900 end_offset = offset + 1
901
902 # Convert 1-based column offset to 0-based index into stripped text
903 colno = offset - 1 - spaces
904 end_colno = end_offset - 1 - spaces
905 if colno >= 0:
906 # non-space whitespace (likes tabs) must be kept for alignment
907 caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno])
908 yield ' {}{}'.format("".join(caretspace), ('^' * (end_colno - colno) + "\n"))
909 msg = self.msg or "<no detail available>"
910 yield "{}: {}{}\n".format(stype, msg, filename_suffix)
911
912 def format(self, *, chain=True, _ctx=None):
913 """Format the exception.
914
915 If chain is not *True*, *__cause__* and *__context__* will not be formatted.
916
917 The return value is a generator of strings, each ending in a newline and
918 some containing internal newlines. `print_exception` is a wrapper around
919 this method which just prints the lines to a file.
920
921 The message indicating which exception occurred is always the last
922 string in the output.
923 """
924
925 if _ctx is None:
926 _ctx = _ExceptionPrintContext()
927
928 output = []
929 exc = self
930 if chain:
931 while exc:
932 if exc.__cause__ is not None:
933 chained_msg = _cause_message
934 chained_exc = exc.__cause__
935 elif (exc.__context__ is not None and
936 not exc.__suppress_context__):
937 chained_msg = _context_message
938 chained_exc = exc.__context__
939 else:
940 chained_msg = None
941 chained_exc = None
942
943 output.append((chained_msg, exc))
944 exc = chained_exc
945 else:
946 output.append((None, exc))
947
948 for msg, exc in reversed(output):
949 if msg is not None:
950 yield from _ctx.emit(msg)
951 if exc.exceptions is None:
952 if exc.stack:
953 yield from _ctx.emit('Traceback (most recent call last):\n')
954 yield from _ctx.emit(exc.stack.format())
955 yield from _ctx.emit(exc.format_exception_only())
956 elif _ctx.exception_group_depth > self.max_group_depth:
957 # exception group, but depth exceeds limit
958 yield from _ctx.emit(
959 f"... (max_group_depth is {self.max_group_depth})\n")
960 else:
961 # format exception group
962 is_toplevel = (_ctx.exception_group_depth == 0)
963 if is_toplevel:
964 _ctx.exception_group_depth += 1
965
966 if exc.stack:
967 yield from _ctx.emit(
968 'Exception Group Traceback (most recent call last):\n',
969 margin_char = '+' if is_toplevel else None)
970 yield from _ctx.emit(exc.stack.format())
971
972 yield from _ctx.emit(exc.format_exception_only())
973 num_excs = len(exc.exceptions)
974 if num_excs <= self.max_group_width:
975 n = num_excs
976 else:
977 n = self.max_group_width + 1
978 _ctx.need_close = False
979 for i in range(n):
980 last_exc = (i == n-1)
981 if last_exc:
982 # The closing frame may be added by a recursive call
983 _ctx.need_close = True
984
985 if self.max_group_width is not None:
986 truncated = (i >= self.max_group_width)
987 else:
988 truncated = False
989 title = f'{i+1}' if not truncated else '...'
990 yield (_ctx.indent() +
991 ('+-' if i==0 else ' ') +
992 f'+---------------- {title} ----------------\n')
993 _ctx.exception_group_depth += 1
994 if not truncated:
995 yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx)
996 else:
997 remaining = num_excs - self.max_group_width
998 plural = 's' if remaining > 1 else ''
999 yield from _ctx.emit(
1000 f"and {remaining} more exception{plural}\n")
1001
1002 if last_exc and _ctx.need_close:
1003 yield (_ctx.indent() +
1004 "+------------------------------------\n")
1005 _ctx.need_close = False
1006 _ctx.exception_group_depth -= 1
1007
1008 if is_toplevel:
1009 assert _ctx.exception_group_depth == 1
1010 _ctx.exception_group_depth = 0
1011
1012
1013 def print(self, *, file=None, chain=True):
1014 """Print the result of self.format(chain=chain) to 'file'."""
1015 if file is None:
1016 file = sys.stderr
1017 for line in self.format(chain=chain):
1018 print(line, file=file, end="")