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