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