1"""Extract, format and print information about Python stack traces.""" 2 3import collections.abc 4import itertools 5import linecache 6import sys 7import textwrap 8import warnings 9from contextlib import suppress 10import _colorize 11from _colorize import ANSIColors 12 13__all__ = ['extract_stack', 'extract_tb', 'format_exception', 14 'format_exception_only', 'format_list', 'format_stack', 15 'format_tb', 'print_exc', 'format_exc', 'print_exception', 16 'print_last', 'print_stack', 'print_tb', 'clear_frames', 17 'FrameSummary', 'StackSummary', 'TracebackException', 18 'walk_stack', 'walk_tb'] 19 20# 21# Formatting and printing lists of traceback lines. 22# 23 24 25def print_list(extracted_list, file=None): 26 """Print the list of tuples as returned by extract_tb() or 27 extract_stack() as a formatted stack trace to the given file.""" 28 if file is None: 29 file = sys.stderr 30 for item in StackSummary.from_list(extracted_list).format(): 31 print(item, file=file, end="") 32 33def format_list(extracted_list): 34 """Format a list of tuples or FrameSummary objects for printing. 35 36 Given a list of tuples or FrameSummary objects as returned by 37 extract_tb() or extract_stack(), return a list of strings ready 38 for printing. 39 40 Each string in the resulting list corresponds to the item with the 41 same index in the argument list. Each string ends in a newline; 42 the strings may contain internal newlines as well, for those items 43 whose source text line is not None. 44 """ 45 return StackSummary.from_list(extracted_list).format() 46 47# 48# Printing and Extracting Tracebacks. 49# 50 51def print_tb(tb, limit=None, file=None): 52 """Print up to 'limit' stack trace entries from the traceback 'tb'. 53 54 If 'limit' is omitted or None, all entries are printed. If 'file' 55 is omitted or None, the output goes to sys.stderr; otherwise 56 'file' should be an open file or file-like object with a write() 57 method. 58 """ 59 print_list(extract_tb(tb, limit=limit), file=file) 60 61def format_tb(tb, limit=None): 62 """A shorthand for 'format_list(extract_tb(tb, limit))'.""" 63 return extract_tb(tb, limit=limit).format() 64 65def extract_tb(tb, limit=None): 66 """ 67 Return a StackSummary object representing a list of 68 pre-processed entries from traceback. 69 70 This is useful for alternate formatting of stack traces. If 71 'limit' is omitted or None, all entries are extracted. A 72 pre-processed stack trace entry is a FrameSummary object 73 containing attributes filename, lineno, name, and line 74 representing the information that is usually printed for a stack 75 trace. The line is a string with leading and trailing 76 whitespace stripped; if the source is not available it is None. 77 """ 78 return StackSummary._extract_from_extended_frame_gen( 79 _walk_tb_with_full_positions(tb), limit=limit) 80 81# 82# Exception formatting and output. 83# 84 85_cause_message = ( 86 "\nThe above exception was the direct cause " 87 "of the following exception:\n\n") 88 89_context_message = ( 90 "\nDuring handling of the above exception, " 91 "another exception occurred:\n\n") 92 93 94class _Sentinel: 95 def __repr__(self): 96 return "<implicit>" 97 98_sentinel = _Sentinel() 99 100def _parse_value_tb(exc, value, tb): 101 if (value is _sentinel) != (tb is _sentinel): 102 raise ValueError("Both or neither of value and tb must be given") 103 if value is tb is _sentinel: 104 if exc is not None: 105 if isinstance(exc, BaseException): 106 return exc, exc.__traceback__ 107 108 raise TypeError(f'Exception expected for value, ' 109 f'{type(exc).__name__} found') 110 else: 111 return None, None 112 return value, tb 113 114 115def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ 116 file=None, chain=True, **kwargs): 117 """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. 118 119 This differs from print_tb() in the following ways: (1) if 120 traceback is not None, it prints a header "Traceback (most recent 121 call last):"; (2) it prints the exception type and value after the 122 stack trace; (3) if type is SyntaxError and value has the 123 appropriate format, it prints the line where the syntax error 124 occurred with a caret on the next line indicating the approximate 125 position of the error. 126 """ 127 colorize = kwargs.get("colorize", False) 128 value, tb = _parse_value_tb(exc, value, tb) 129 te = TracebackException(type(value), value, tb, limit=limit, compact=True) 130 te.print(file=file, chain=chain, colorize=colorize) 131 132 133BUILTIN_EXCEPTION_LIMIT = object() 134 135 136def _print_exception_bltin(exc, /): 137 file = sys.stderr if sys.stderr is not None else sys.__stderr__ 138 colorize = _colorize.can_colorize() 139 return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) 140 141 142def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ 143 chain=True, **kwargs): 144 """Format a stack trace and the exception information. 145 146 The arguments have the same meaning as the corresponding arguments 147 to print_exception(). The return value is a list of strings, each 148 ending in a newline and some containing internal newlines. When 149 these lines are concatenated and printed, exactly the same text is 150 printed as does print_exception(). 151 """ 152 colorize = kwargs.get("colorize", False) 153 value, tb = _parse_value_tb(exc, value, tb) 154 te = TracebackException(type(value), value, tb, limit=limit, compact=True) 155 return list(te.format(chain=chain, colorize=colorize)) 156 157 158def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs): 159 """Format the exception part of a traceback. 160 161 The return value is a list of strings, each ending in a newline. 162 163 The list contains the exception's message, which is 164 normally a single string; however, for :exc:`SyntaxError` exceptions, it 165 contains several lines that (when printed) display detailed information 166 about where the syntax error occurred. Following the message, the list 167 contains the exception's ``__notes__``. 168 169 When *show_group* is ``True``, and the exception is an instance of 170 :exc:`BaseExceptionGroup`, the nested exceptions are included as 171 well, recursively, with indentation relative to their nesting depth. 172 """ 173 colorize = kwargs.get("colorize", False) 174 if value is _sentinel: 175 value = exc 176 te = TracebackException(type(value), value, None, compact=True) 177 return list(te.format_exception_only(show_group=show_group, colorize=colorize)) 178 179 180# -- not official API but folk probably use these two functions. 181 182def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=False): 183 valuestr = _safe_string(value, 'exception') 184 end_char = "\n" if insert_final_newline else "" 185 if colorize: 186 if value is None or not valuestr: 187 line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}" 188 else: 189 line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}" 190 else: 191 if value is None or not valuestr: 192 line = f"{etype}{end_char}" 193 else: 194 line = f"{etype}: {valuestr}{end_char}" 195 return line 196 197 198def _safe_string(value, what, func=str): 199 try: 200 return func(value) 201 except: 202 return f'<{what} {func.__name__}() failed>' 203 204# -- 205 206def print_exc(limit=None, file=None, chain=True): 207 """Shorthand for 'print_exception(sys.exception(), limit, file, chain)'.""" 208 print_exception(sys.exception(), limit=limit, file=file, chain=chain) 209 210def format_exc(limit=None, chain=True): 211 """Like print_exc() but return a string.""" 212 return "".join(format_exception(sys.exception(), limit=limit, chain=chain)) 213 214def print_last(limit=None, file=None, chain=True): 215 """This is a shorthand for 'print_exception(sys.last_exc, limit, file, chain)'.""" 216 if not hasattr(sys, "last_exc") and not hasattr(sys, "last_type"): 217 raise ValueError("no last exception") 218 219 if hasattr(sys, "last_exc"): 220 print_exception(sys.last_exc, limit, file, chain) 221 else: 222 print_exception(sys.last_type, sys.last_value, sys.last_traceback, 223 limit, file, chain) 224 225 226# 227# Printing and Extracting Stacks. 228# 229 230def print_stack(f=None, limit=None, file=None): 231 """Print a stack trace from its invocation point. 232 233 The optional 'f' argument can be used to specify an alternate 234 stack frame at which to start. The optional 'limit' and 'file' 235 arguments have the same meaning as for print_exception(). 236 """ 237 if f is None: 238 f = sys._getframe().f_back 239 print_list(extract_stack(f, limit=limit), file=file) 240 241 242def format_stack(f=None, limit=None): 243 """Shorthand for 'format_list(extract_stack(f, limit))'.""" 244 if f is None: 245 f = sys._getframe().f_back 246 return format_list(extract_stack(f, limit=limit)) 247 248 249def extract_stack(f=None, limit=None): 250 """Extract the raw traceback from the current stack frame. 251 252 The return value has the same format as for extract_tb(). The 253 optional 'f' and 'limit' arguments have the same meaning as for 254 print_stack(). Each item in the list is a quadruple (filename, 255 line number, function name, text), and the entries are in order 256 from oldest to newest stack frame. 257 """ 258 if f is None: 259 f = sys._getframe().f_back 260 stack = StackSummary.extract(walk_stack(f), limit=limit) 261 stack.reverse() 262 return stack 263 264 265def clear_frames(tb): 266 "Clear all references to local variables in the frames of a traceback." 267 while tb is not None: 268 try: 269 tb.tb_frame.clear() 270 except RuntimeError: 271 # Ignore the exception raised if the frame is still executing. 272 pass 273 tb = tb.tb_next 274 275 276class FrameSummary: 277 """Information about a single frame from a traceback. 278 279 - :attr:`filename` The filename for the frame. 280 - :attr:`lineno` The line within filename for the frame that was 281 active when the frame was captured. 282 - :attr:`name` The name of the function or method that was executing 283 when the frame was captured. 284 - :attr:`line` The text from the linecache module for the 285 of code that was running when the frame was captured. 286 - :attr:`locals` Either None if locals were not supplied, or a dict 287 mapping the name to the repr() of the variable. 288 """ 289 290 __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno', 291 'name', '_lines', '_lines_dedented', 'locals') 292 293 def __init__(self, filename, lineno, name, *, lookup_line=True, 294 locals=None, line=None, 295 end_lineno=None, colno=None, end_colno=None): 296 """Construct a FrameSummary. 297 298 :param lookup_line: If True, `linecache` is consulted for the source 299 code line. Otherwise, the line will be looked up when first needed. 300 :param locals: If supplied the frame locals, which will be captured as 301 object representations. 302 :param line: If provided, use this instead of looking up the line in 303 the linecache. 304 """ 305 self.filename = filename 306 self.lineno = lineno 307 self.end_lineno = lineno if end_lineno is None else end_lineno 308 self.colno = colno 309 self.end_colno = end_colno 310 self.name = name 311 self._lines = line 312 self._lines_dedented = None 313 if lookup_line: 314 self.line 315 self.locals = {k: _safe_string(v, 'local', func=repr) 316 for k, v in locals.items()} if locals else None 317 318 def __eq__(self, other): 319 if isinstance(other, FrameSummary): 320 return (self.filename == other.filename and 321 self.lineno == other.lineno and 322 self.name == other.name and 323 self.locals == other.locals) 324 if isinstance(other, tuple): 325 return (self.filename, self.lineno, self.name, self.line) == other 326 return NotImplemented 327 328 def __getitem__(self, pos): 329 return (self.filename, self.lineno, self.name, self.line)[pos] 330 331 def __iter__(self): 332 return iter([self.filename, self.lineno, self.name, self.line]) 333 334 def __repr__(self): 335 return "<FrameSummary file {filename}, line {lineno} in {name}>".format( 336 filename=self.filename, lineno=self.lineno, name=self.name) 337 338 def __len__(self): 339 return 4 340 341 def _set_lines(self): 342 if ( 343 self._lines is None 344 and self.lineno is not None 345 and self.end_lineno is not None 346 ): 347 lines = [] 348 for lineno in range(self.lineno, self.end_lineno + 1): 349 # treat errors (empty string) and empty lines (newline) as the same 350 lines.append(linecache.getline(self.filename, lineno).rstrip()) 351 self._lines = "\n".join(lines) + "\n" 352 353 @property 354 def _original_lines(self): 355 # Returns the line as-is from the source, without modifying whitespace. 356 self._set_lines() 357 return self._lines 358 359 @property 360 def _dedented_lines(self): 361 # Returns _original_lines, but dedented 362 self._set_lines() 363 if self._lines_dedented is None and self._lines is not None: 364 self._lines_dedented = textwrap.dedent(self._lines) 365 return self._lines_dedented 366 367 @property 368 def line(self): 369 self._set_lines() 370 if self._lines is None: 371 return None 372 # return only the first line, stripped 373 return self._lines.partition("\n")[0].strip() 374 375 376def walk_stack(f): 377 """Walk a stack yielding the frame and line number for each frame. 378 379 This will follow f.f_back from the given frame. If no frame is given, the 380 current stack is used. Usually used with StackSummary.extract. 381 """ 382 if f is None: 383 f = sys._getframe().f_back.f_back.f_back.f_back 384 while f is not None: 385 yield f, f.f_lineno 386 f = f.f_back 387 388 389def walk_tb(tb): 390 """Walk a traceback yielding the frame and line number for each frame. 391 392 This will follow tb.tb_next (and thus is in the opposite order to 393 walk_stack). Usually used with StackSummary.extract. 394 """ 395 while tb is not None: 396 yield tb.tb_frame, tb.tb_lineno 397 tb = tb.tb_next 398 399 400def _walk_tb_with_full_positions(tb): 401 # Internal version of walk_tb that yields full code positions including 402 # end line and column information. 403 while tb is not None: 404 positions = _get_code_position(tb.tb_frame.f_code, tb.tb_lasti) 405 # Yield tb_lineno when co_positions does not have a line number to 406 # maintain behavior with walk_tb. 407 if positions[0] is None: 408 yield tb.tb_frame, (tb.tb_lineno, ) + positions[1:] 409 else: 410 yield tb.tb_frame, positions 411 tb = tb.tb_next 412 413 414def _get_code_position(code, instruction_index): 415 if instruction_index < 0: 416 return (None, None, None, None) 417 positions_gen = code.co_positions() 418 return next(itertools.islice(positions_gen, instruction_index // 2, None)) 419 420 421_RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c. 422 423 424class StackSummary(list): 425 """A list of FrameSummary objects, representing a stack of frames.""" 426 427 @classmethod 428 def extract(klass, frame_gen, *, limit=None, lookup_lines=True, 429 capture_locals=False): 430 """Create a StackSummary from a traceback or stack object. 431 432 :param frame_gen: A generator that yields (frame, lineno) tuples 433 whose summaries are to be included in the stack. 434 :param limit: None to include all frames or the number of frames to 435 include. 436 :param lookup_lines: If True, lookup lines for each frame immediately, 437 otherwise lookup is deferred until the frame is rendered. 438 :param capture_locals: If True, the local variables from each frame will 439 be captured as object representations into the FrameSummary. 440 """ 441 def extended_frame_gen(): 442 for f, lineno in frame_gen: 443 yield f, (lineno, None, None, None) 444 445 return klass._extract_from_extended_frame_gen( 446 extended_frame_gen(), limit=limit, lookup_lines=lookup_lines, 447 capture_locals=capture_locals) 448 449 @classmethod 450 def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None, 451 lookup_lines=True, capture_locals=False): 452 # Same as extract but operates on a frame generator that yields 453 # (frame, (lineno, end_lineno, colno, end_colno)) in the stack. 454 # Only lineno is required, the remaining fields can be None if the 455 # information is not available. 456 builtin_limit = limit is BUILTIN_EXCEPTION_LIMIT 457 if limit is None or builtin_limit: 458 limit = getattr(sys, 'tracebacklimit', None) 459 if limit is not None and limit < 0: 460 limit = 0 461 if limit is not None: 462 if builtin_limit: 463 frame_gen = tuple(frame_gen) 464 frame_gen = frame_gen[len(frame_gen) - limit:] 465 elif limit >= 0: 466 frame_gen = itertools.islice(frame_gen, limit) 467 else: 468 frame_gen = collections.deque(frame_gen, maxlen=-limit) 469 470 result = klass() 471 fnames = set() 472 for f, (lineno, end_lineno, colno, end_colno) in frame_gen: 473 co = f.f_code 474 filename = co.co_filename 475 name = co.co_name 476 fnames.add(filename) 477 linecache.lazycache(filename, f.f_globals) 478 # Must defer line lookups until we have called checkcache. 479 if capture_locals: 480 f_locals = f.f_locals 481 else: 482 f_locals = None 483 result.append(FrameSummary( 484 filename, lineno, name, lookup_line=False, locals=f_locals, 485 end_lineno=end_lineno, colno=colno, end_colno=end_colno)) 486 for filename in fnames: 487 linecache.checkcache(filename) 488 489 # If immediate lookup was desired, trigger lookups now. 490 if lookup_lines: 491 for f in result: 492 f.line 493 return result 494 495 @classmethod 496 def from_list(klass, a_list): 497 """ 498 Create a StackSummary object from a supplied list of 499 FrameSummary objects or old-style list of tuples. 500 """ 501 # While doing a fast-path check for isinstance(a_list, StackSummary) is 502 # appealing, idlelib.run.cleanup_traceback and other similar code may 503 # break this by making arbitrary frames plain tuples, so we need to 504 # check on a frame by frame basis. 505 result = StackSummary() 506 for frame in a_list: 507 if isinstance(frame, FrameSummary): 508 result.append(frame) 509 else: 510 filename, lineno, name, line = frame 511 result.append(FrameSummary(filename, lineno, name, line=line)) 512 return result 513 514 def format_frame_summary(self, frame_summary, **kwargs): 515 """Format the lines for a single FrameSummary. 516 517 Returns a string representing one frame involved in the stack. This 518 gets called for every frame to be printed in the stack summary. 519 """ 520 colorize = kwargs.get("colorize", False) 521 row = [] 522 filename = frame_summary.filename 523 if frame_summary.filename.startswith("<stdin>-"): 524 filename = "<stdin>" 525 if colorize: 526 row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format( 527 ANSIColors.MAGENTA, 528 filename, 529 ANSIColors.RESET, 530 ANSIColors.MAGENTA, 531 frame_summary.lineno, 532 ANSIColors.RESET, 533 ANSIColors.MAGENTA, 534 frame_summary.name, 535 ANSIColors.RESET, 536 ) 537 ) 538 else: 539 row.append(' File "{}", line {}, in {}\n'.format( 540 filename, frame_summary.lineno, frame_summary.name)) 541 if frame_summary._dedented_lines and frame_summary._dedented_lines.strip(): 542 if ( 543 frame_summary.colno is None or 544 frame_summary.end_colno is None 545 ): 546 # only output first line if column information is missing 547 row.append(textwrap.indent(frame_summary.line, ' ') + "\n") 548 else: 549 # get first and last line 550 all_lines_original = frame_summary._original_lines.splitlines() 551 first_line = all_lines_original[0] 552 # assume all_lines_original has enough lines (since we constructed it) 553 last_line = all_lines_original[frame_summary.end_lineno - frame_summary.lineno] 554 555 # character index of the start/end of the instruction 556 start_offset = _byte_offset_to_character_offset(first_line, frame_summary.colno) 557 end_offset = _byte_offset_to_character_offset(last_line, frame_summary.end_colno) 558 559 all_lines = frame_summary._dedented_lines.splitlines()[ 560 :frame_summary.end_lineno - frame_summary.lineno + 1 561 ] 562 563 # adjust start/end offset based on dedent 564 dedent_characters = len(first_line) - len(all_lines[0]) 565 start_offset = max(0, start_offset - dedent_characters) 566 end_offset = max(0, end_offset - dedent_characters) 567 568 # When showing this on a terminal, some of the non-ASCII characters 569 # might be rendered as double-width characters, so we need to take 570 # that into account when calculating the length of the line. 571 dp_start_offset = _display_width(all_lines[0], offset=start_offset) 572 dp_end_offset = _display_width(all_lines[-1], offset=end_offset) 573 574 # get exact code segment corresponding to the instruction 575 segment = "\n".join(all_lines) 576 segment = segment[start_offset:len(segment) - (len(all_lines[-1]) - end_offset)] 577 578 # attempt to parse for anchors 579 anchors = None 580 show_carets = False 581 with suppress(Exception): 582 anchors = _extract_caret_anchors_from_line_segment(segment) 583 show_carets = self._should_show_carets(start_offset, end_offset, all_lines, anchors) 584 585 result = [] 586 587 # only display first line, last line, and lines around anchor start/end 588 significant_lines = {0, len(all_lines) - 1} 589 590 anchors_left_end_offset = 0 591 anchors_right_start_offset = 0 592 primary_char = "^" 593 secondary_char = "^" 594 if anchors: 595 anchors_left_end_offset = anchors.left_end_offset 596 anchors_right_start_offset = anchors.right_start_offset 597 # computed anchor positions do not take start_offset into account, 598 # so account for it here 599 if anchors.left_end_lineno == 0: 600 anchors_left_end_offset += start_offset 601 if anchors.right_start_lineno == 0: 602 anchors_right_start_offset += start_offset 603 604 # account for display width 605 anchors_left_end_offset = _display_width( 606 all_lines[anchors.left_end_lineno], offset=anchors_left_end_offset 607 ) 608 anchors_right_start_offset = _display_width( 609 all_lines[anchors.right_start_lineno], offset=anchors_right_start_offset 610 ) 611 612 primary_char = anchors.primary_char 613 secondary_char = anchors.secondary_char 614 significant_lines.update( 615 range(anchors.left_end_lineno - 1, anchors.left_end_lineno + 2) 616 ) 617 significant_lines.update( 618 range(anchors.right_start_lineno - 1, anchors.right_start_lineno + 2) 619 ) 620 621 # remove bad line numbers 622 significant_lines.discard(-1) 623 significant_lines.discard(len(all_lines)) 624 625 def output_line(lineno): 626 """output all_lines[lineno] along with carets""" 627 result.append(all_lines[lineno] + "\n") 628 if not show_carets: 629 return 630 num_spaces = len(all_lines[lineno]) - len(all_lines[lineno].lstrip()) 631 carets = [] 632 num_carets = dp_end_offset if lineno == len(all_lines) - 1 else _display_width(all_lines[lineno]) 633 # compute caret character for each position 634 for col in range(num_carets): 635 if col < num_spaces or (lineno == 0 and col < dp_start_offset): 636 # before first non-ws char of the line, or before start of instruction 637 carets.append(' ') 638 elif anchors and ( 639 lineno > anchors.left_end_lineno or 640 (lineno == anchors.left_end_lineno and col >= anchors_left_end_offset) 641 ) and ( 642 lineno < anchors.right_start_lineno or 643 (lineno == anchors.right_start_lineno and col < anchors_right_start_offset) 644 ): 645 # within anchors 646 carets.append(secondary_char) 647 else: 648 carets.append(primary_char) 649 if colorize: 650 # Replace the previous line with a red version of it only in the parts covered 651 # by the carets. 652 line = result[-1] 653 colorized_line_parts = [] 654 colorized_carets_parts = [] 655 656 for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]): 657 caret_group = list(group) 658 if color == "^": 659 colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) 660 colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) 661 elif color == "~": 662 colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET) 663 colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET) 664 else: 665 colorized_line_parts.append("".join(char for char, _ in caret_group)) 666 colorized_carets_parts.append("".join(caret for _, caret in caret_group)) 667 668 colorized_line = "".join(colorized_line_parts) 669 colorized_carets = "".join(colorized_carets_parts) 670 result[-1] = colorized_line 671 result.append(colorized_carets + "\n") 672 else: 673 result.append("".join(carets) + "\n") 674 675 # display significant lines 676 sig_lines_list = sorted(significant_lines) 677 for i, lineno in enumerate(sig_lines_list): 678 if i: 679 linediff = lineno - sig_lines_list[i - 1] 680 if linediff == 2: 681 # 1 line in between - just output it 682 output_line(lineno - 1) 683 elif linediff > 2: 684 # > 1 line in between - abbreviate 685 result.append(f"...<{linediff - 1} lines>...\n") 686 output_line(lineno) 687 688 row.append( 689 textwrap.indent(textwrap.dedent("".join(result)), ' ', lambda line: True) 690 ) 691 if frame_summary.locals: 692 for name, value in sorted(frame_summary.locals.items()): 693 row.append(' {name} = {value}\n'.format(name=name, value=value)) 694 695 return ''.join(row) 696 697 def _should_show_carets(self, start_offset, end_offset, all_lines, anchors): 698 with suppress(SyntaxError, ImportError): 699 import ast 700 tree = ast.parse('\n'.join(all_lines)) 701 if not tree.body: 702 return False 703 statement = tree.body[0] 704 value = None 705 def _spawns_full_line(value): 706 return ( 707 value.lineno == 1 708 and value.end_lineno == len(all_lines) 709 and value.col_offset == start_offset 710 and value.end_col_offset == end_offset 711 ) 712 match statement: 713 case ast.Return(value=ast.Call()): 714 if isinstance(statement.value.func, ast.Name): 715 value = statement.value 716 case ast.Assign(value=ast.Call()): 717 if ( 718 len(statement.targets) == 1 and 719 isinstance(statement.targets[0], ast.Name) 720 ): 721 value = statement.value 722 if value is not None and _spawns_full_line(value): 723 return False 724 if anchors: 725 return True 726 if all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip(): 727 return True 728 return False 729 730 def format(self, **kwargs): 731 """Format the stack ready for printing. 732 733 Returns a list of strings ready for printing. Each string in the 734 resulting list corresponds to a single frame from the stack. 735 Each string ends in a newline; the strings may contain internal 736 newlines as well, for those items with source text lines. 737 738 For long sequences of the same frame and line, the first few 739 repetitions are shown, followed by a summary line stating the exact 740 number of further repetitions. 741 """ 742 colorize = kwargs.get("colorize", False) 743 result = [] 744 last_file = None 745 last_line = None 746 last_name = None 747 count = 0 748 for frame_summary in self: 749 formatted_frame = self.format_frame_summary(frame_summary, colorize=colorize) 750 if formatted_frame is None: 751 continue 752 if (last_file is None or last_file != frame_summary.filename or 753 last_line is None or last_line != frame_summary.lineno or 754 last_name is None or last_name != frame_summary.name): 755 if count > _RECURSIVE_CUTOFF: 756 count -= _RECURSIVE_CUTOFF 757 result.append( 758 f' [Previous line repeated {count} more ' 759 f'time{"s" if count > 1 else ""}]\n' 760 ) 761 last_file = frame_summary.filename 762 last_line = frame_summary.lineno 763 last_name = frame_summary.name 764 count = 0 765 count += 1 766 if count > _RECURSIVE_CUTOFF: 767 continue 768 result.append(formatted_frame) 769 770 if count > _RECURSIVE_CUTOFF: 771 count -= _RECURSIVE_CUTOFF 772 result.append( 773 f' [Previous line repeated {count} more ' 774 f'time{"s" if count > 1 else ""}]\n' 775 ) 776 return result 777 778 779def _byte_offset_to_character_offset(str, offset): 780 as_utf8 = str.encode('utf-8') 781 return len(as_utf8[:offset].decode("utf-8", errors="replace")) 782 783 784_Anchors = collections.namedtuple( 785 "_Anchors", 786 [ 787 "left_end_lineno", 788 "left_end_offset", 789 "right_start_lineno", 790 "right_start_offset", 791 "primary_char", 792 "secondary_char", 793 ], 794 defaults=["~", "^"] 795) 796 797def _extract_caret_anchors_from_line_segment(segment): 798 """ 799 Given source code `segment` corresponding to a FrameSummary, determine: 800 - for binary ops, the location of the binary op 801 - for indexing and function calls, the location of the brackets. 802 `segment` is expected to be a valid Python expression. 803 """ 804 import ast 805 806 try: 807 # Without parentheses, `segment` is parsed as a statement. 808 # Binary ops, subscripts, and calls are expressions, so 809 # we can wrap them with parentheses to parse them as 810 # (possibly multi-line) expressions. 811 # e.g. if we try to highlight the addition in 812 # x = ( 813 # a + 814 # b 815 # ) 816 # then we would ast.parse 817 # a + 818 # b 819 # which is not a valid statement because of the newline. 820 # Adding brackets makes it a valid expression. 821 # ( 822 # a + 823 # b 824 # ) 825 # Line locations will be different than the original, 826 # which is taken into account later on. 827 tree = ast.parse(f"(\n{segment}\n)") 828 except SyntaxError: 829 return None 830 831 if len(tree.body) != 1: 832 return None 833 834 lines = segment.splitlines() 835 836 def normalize(lineno, offset): 837 """Get character index given byte offset""" 838 return _byte_offset_to_character_offset(lines[lineno], offset) 839 840 def next_valid_char(lineno, col): 841 """Gets the next valid character index in `lines`, if 842 the current location is not valid. Handles empty lines. 843 """ 844 while lineno < len(lines) and col >= len(lines[lineno]): 845 col = 0 846 lineno += 1 847 assert lineno < len(lines) and col < len(lines[lineno]) 848 return lineno, col 849 850 def increment(lineno, col): 851 """Get the next valid character index in `lines`.""" 852 col += 1 853 lineno, col = next_valid_char(lineno, col) 854 return lineno, col 855 856 def nextline(lineno, col): 857 """Get the next valid character at least on the next line""" 858 col = 0 859 lineno += 1 860 lineno, col = next_valid_char(lineno, col) 861 return lineno, col 862 863 def increment_until(lineno, col, stop): 864 """Get the next valid non-"\\#" character that satisfies the `stop` predicate""" 865 while True: 866 ch = lines[lineno][col] 867 if ch in "\\#": 868 lineno, col = nextline(lineno, col) 869 elif not stop(ch): 870 lineno, col = increment(lineno, col) 871 else: 872 break 873 return lineno, col 874 875 def setup_positions(expr, force_valid=True): 876 """Get the lineno/col position of the end of `expr`. If `force_valid` is True, 877 forces the position to be a valid character (e.g. if the position is beyond the 878 end of the line, move to the next line) 879 """ 880 # -2 since end_lineno is 1-indexed and because we added an extra 881 # bracket + newline to `segment` when calling ast.parse 882 lineno = expr.end_lineno - 2 883 col = normalize(lineno, expr.end_col_offset) 884 return next_valid_char(lineno, col) if force_valid else (lineno, col) 885 886 statement = tree.body[0] 887 match statement: 888 case ast.Expr(expr): 889 match expr: 890 case ast.BinOp(): 891 # ast gives these locations for BinOp subexpressions 892 # ( left_expr ) + ( right_expr ) 893 # left^^^^^ right^^^^^ 894 lineno, col = setup_positions(expr.left) 895 896 # First operator character is the first non-space/')' character 897 lineno, col = increment_until(lineno, col, lambda x: not x.isspace() and x != ')') 898 899 # binary op is 1 or 2 characters long, on the same line, 900 # before the right subexpression 901 right_col = col + 1 902 if ( 903 right_col < len(lines[lineno]) 904 and ( 905 # operator char should not be in the right subexpression 906 expr.right.lineno - 2 > lineno or 907 right_col < normalize(expr.right.lineno - 2, expr.right.col_offset) 908 ) 909 and not (ch := lines[lineno][right_col]).isspace() 910 and ch not in "\\#" 911 ): 912 right_col += 1 913 914 # right_col can be invalid since it is exclusive 915 return _Anchors(lineno, col, lineno, right_col) 916 case ast.Subscript(): 917 # ast gives these locations for value and slice subexpressions 918 # ( value_expr ) [ slice_expr ] 919 # value^^^^^ slice^^^^^ 920 # subscript^^^^^^^^^^^^^^^^^^^^ 921 922 # find left bracket 923 left_lineno, left_col = setup_positions(expr.value) 924 left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '[') 925 # find right bracket (final character of expression) 926 right_lineno, right_col = setup_positions(expr, force_valid=False) 927 return _Anchors(left_lineno, left_col, right_lineno, right_col) 928 case ast.Call(): 929 # ast gives these locations for function call expressions 930 # ( func_expr ) (args, kwargs) 931 # func^^^^^ 932 # call^^^^^^^^^^^^^^^^^^^^^^^^ 933 934 # find left bracket 935 left_lineno, left_col = setup_positions(expr.func) 936 left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '(') 937 # find right bracket (final character of expression) 938 right_lineno, right_col = setup_positions(expr, force_valid=False) 939 return _Anchors(left_lineno, left_col, right_lineno, right_col) 940 941 return None 942 943_WIDE_CHAR_SPECIFIERS = "WF" 944 945def _display_width(line, offset=None): 946 """Calculate the extra amount of width space the given source 947 code segment might take if it were to be displayed on a fixed 948 width output device. Supports wide unicode characters and emojis.""" 949 950 if offset is None: 951 offset = len(line) 952 953 # Fast track for ASCII-only strings 954 if line.isascii(): 955 return offset 956 957 import unicodedata 958 959 return sum( 960 2 if unicodedata.east_asian_width(char) in _WIDE_CHAR_SPECIFIERS else 1 961 for char in line[:offset] 962 ) 963 964 965 966class _ExceptionPrintContext: 967 def __init__(self): 968 self.seen = set() 969 self.exception_group_depth = 0 970 self.need_close = False 971 972 def indent(self): 973 return ' ' * (2 * self.exception_group_depth) 974 975 def emit(self, text_gen, margin_char=None): 976 if margin_char is None: 977 margin_char = '|' 978 indent_str = self.indent() 979 if self.exception_group_depth: 980 indent_str += margin_char + ' ' 981 982 if isinstance(text_gen, str): 983 yield textwrap.indent(text_gen, indent_str, lambda line: True) 984 else: 985 for text in text_gen: 986 yield textwrap.indent(text, indent_str, lambda line: True) 987 988 989class TracebackException: 990 """An exception ready for rendering. 991 992 The traceback module captures enough attributes from the original exception 993 to this intermediary form to ensure that no references are held, while 994 still being able to fully print or format it. 995 996 max_group_width and max_group_depth control the formatting of exception 997 groups. The depth refers to the nesting level of the group, and the width 998 refers to the size of a single exception group's exceptions array. The 999 formatted output is truncated when either limit is exceeded. 1000 1001 Use `from_exception` to create TracebackException instances from exception 1002 objects, or the constructor to create TracebackException instances from 1003 individual components. 1004 1005 - :attr:`__cause__` A TracebackException of the original *__cause__*. 1006 - :attr:`__context__` A TracebackException of the original *__context__*. 1007 - :attr:`exceptions` For exception groups - a list of TracebackException 1008 instances for the nested *exceptions*. ``None`` for other exceptions. 1009 - :attr:`__suppress_context__` The *__suppress_context__* value from the 1010 original exception. 1011 - :attr:`stack` A `StackSummary` representing the traceback. 1012 - :attr:`exc_type` (deprecated) The class of the original traceback. 1013 - :attr:`exc_type_str` String display of exc_type 1014 - :attr:`filename` For syntax errors - the filename where the error 1015 occurred. 1016 - :attr:`lineno` For syntax errors - the linenumber where the error 1017 occurred. 1018 - :attr:`end_lineno` For syntax errors - the end linenumber where the error 1019 occurred. Can be `None` if not present. 1020 - :attr:`text` For syntax errors - the text where the error 1021 occurred. 1022 - :attr:`offset` For syntax errors - the offset into the text where the 1023 error occurred. 1024 - :attr:`end_offset` For syntax errors - the end offset into the text where 1025 the error occurred. Can be `None` if not present. 1026 - :attr:`msg` For syntax errors - the compiler error message. 1027 """ 1028 1029 def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, 1030 lookup_lines=True, capture_locals=False, compact=False, 1031 max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None): 1032 # NB: we need to accept exc_traceback, exc_value, exc_traceback to 1033 # permit backwards compat with the existing API, otherwise we 1034 # need stub thunk objects just to glue it together. 1035 # Handle loops in __cause__ or __context__. 1036 is_recursive_call = _seen is not None 1037 if _seen is None: 1038 _seen = set() 1039 _seen.add(id(exc_value)) 1040 1041 self.max_group_width = max_group_width 1042 self.max_group_depth = max_group_depth 1043 1044 self.stack = StackSummary._extract_from_extended_frame_gen( 1045 _walk_tb_with_full_positions(exc_traceback), 1046 limit=limit, lookup_lines=lookup_lines, 1047 capture_locals=capture_locals) 1048 1049 self._exc_type = exc_type if save_exc_type else None 1050 1051 # Capture now to permit freeing resources: only complication is in the 1052 # unofficial API _format_final_exc_line 1053 self._str = _safe_string(exc_value, 'exception') 1054 try: 1055 self.__notes__ = getattr(exc_value, '__notes__', None) 1056 except Exception as e: 1057 self.__notes__ = [ 1058 f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}'] 1059 1060 self._is_syntax_error = False 1061 self._have_exc_type = exc_type is not None 1062 if exc_type is not None: 1063 self.exc_type_qualname = exc_type.__qualname__ 1064 self.exc_type_module = exc_type.__module__ 1065 else: 1066 self.exc_type_qualname = None 1067 self.exc_type_module = None 1068 1069 if exc_type and issubclass(exc_type, SyntaxError): 1070 # Handle SyntaxError's specially 1071 self.filename = exc_value.filename 1072 lno = exc_value.lineno 1073 self.lineno = str(lno) if lno is not None else None 1074 end_lno = exc_value.end_lineno 1075 self.end_lineno = str(end_lno) if end_lno is not None else None 1076 self.text = exc_value.text 1077 self.offset = exc_value.offset 1078 self.end_offset = exc_value.end_offset 1079 self.msg = exc_value.msg 1080 self._is_syntax_error = True 1081 elif exc_type and issubclass(exc_type, ImportError) and \ 1082 getattr(exc_value, "name_from", None) is not None: 1083 wrong_name = getattr(exc_value, "name_from", None) 1084 suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name) 1085 if suggestion: 1086 self._str += f". Did you mean: '{suggestion}'?" 1087 elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \ 1088 getattr(exc_value, "name", None) is not None: 1089 wrong_name = getattr(exc_value, "name", None) 1090 suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name) 1091 if suggestion: 1092 self._str += f". Did you mean: '{suggestion}'?" 1093 if issubclass(exc_type, NameError): 1094 wrong_name = getattr(exc_value, "name", None) 1095 if wrong_name is not None and wrong_name in sys.stdlib_module_names: 1096 if suggestion: 1097 self._str += f" Or did you forget to import '{wrong_name}'?" 1098 else: 1099 self._str += f". Did you forget to import '{wrong_name}'?" 1100 if lookup_lines: 1101 self._load_lines() 1102 self.__suppress_context__ = \ 1103 exc_value.__suppress_context__ if exc_value is not None else False 1104 1105 # Convert __cause__ and __context__ to `TracebackExceptions`s, use a 1106 # queue to avoid recursion (only the top-level call gets _seen == None) 1107 if not is_recursive_call: 1108 queue = [(self, exc_value)] 1109 while queue: 1110 te, e = queue.pop() 1111 if (e and e.__cause__ is not None 1112 and id(e.__cause__) not in _seen): 1113 cause = TracebackException( 1114 type(e.__cause__), 1115 e.__cause__, 1116 e.__cause__.__traceback__, 1117 limit=limit, 1118 lookup_lines=lookup_lines, 1119 capture_locals=capture_locals, 1120 max_group_width=max_group_width, 1121 max_group_depth=max_group_depth, 1122 _seen=_seen) 1123 else: 1124 cause = None 1125 1126 if compact: 1127 need_context = (cause is None and 1128 e is not None and 1129 not e.__suppress_context__) 1130 else: 1131 need_context = True 1132 if (e and e.__context__ is not None 1133 and need_context and id(e.__context__) not in _seen): 1134 context = TracebackException( 1135 type(e.__context__), 1136 e.__context__, 1137 e.__context__.__traceback__, 1138 limit=limit, 1139 lookup_lines=lookup_lines, 1140 capture_locals=capture_locals, 1141 max_group_width=max_group_width, 1142 max_group_depth=max_group_depth, 1143 _seen=_seen) 1144 else: 1145 context = None 1146 1147 if e and isinstance(e, BaseExceptionGroup): 1148 exceptions = [] 1149 for exc in e.exceptions: 1150 texc = TracebackException( 1151 type(exc), 1152 exc, 1153 exc.__traceback__, 1154 limit=limit, 1155 lookup_lines=lookup_lines, 1156 capture_locals=capture_locals, 1157 max_group_width=max_group_width, 1158 max_group_depth=max_group_depth, 1159 _seen=_seen) 1160 exceptions.append(texc) 1161 else: 1162 exceptions = None 1163 1164 te.__cause__ = cause 1165 te.__context__ = context 1166 te.exceptions = exceptions 1167 if cause: 1168 queue.append((te.__cause__, e.__cause__)) 1169 if context: 1170 queue.append((te.__context__, e.__context__)) 1171 if exceptions: 1172 queue.extend(zip(te.exceptions, e.exceptions)) 1173 1174 @classmethod 1175 def from_exception(cls, exc, *args, **kwargs): 1176 """Create a TracebackException from an exception.""" 1177 return cls(type(exc), exc, exc.__traceback__, *args, **kwargs) 1178 1179 @property 1180 def exc_type(self): 1181 warnings.warn('Deprecated in 3.13. Use exc_type_str instead.', 1182 DeprecationWarning, stacklevel=2) 1183 return self._exc_type 1184 1185 @property 1186 def exc_type_str(self): 1187 if not self._have_exc_type: 1188 return None 1189 stype = self.exc_type_qualname 1190 smod = self.exc_type_module 1191 if smod not in ("__main__", "builtins"): 1192 if not isinstance(smod, str): 1193 smod = "<unknown>" 1194 stype = smod + '.' + stype 1195 return stype 1196 1197 def _load_lines(self): 1198 """Private API. force all lines in the stack to be loaded.""" 1199 for frame in self.stack: 1200 frame.line 1201 1202 def __eq__(self, other): 1203 if isinstance(other, TracebackException): 1204 return self.__dict__ == other.__dict__ 1205 return NotImplemented 1206 1207 def __str__(self): 1208 return self._str 1209 1210 def format_exception_only(self, *, show_group=False, _depth=0, **kwargs): 1211 """Format the exception part of the traceback. 1212 1213 The return value is a generator of strings, each ending in a newline. 1214 1215 Generator yields the exception message. 1216 For :exc:`SyntaxError` exceptions, it 1217 also yields (before the exception message) 1218 several lines that (when printed) 1219 display detailed information about where the syntax error occurred. 1220 Following the message, generator also yields 1221 all the exception's ``__notes__``. 1222 1223 When *show_group* is ``True``, and the exception is an instance of 1224 :exc:`BaseExceptionGroup`, the nested exceptions are included as 1225 well, recursively, with indentation relative to their nesting depth. 1226 """ 1227 colorize = kwargs.get("colorize", False) 1228 1229 indent = 3 * _depth * ' ' 1230 if not self._have_exc_type: 1231 yield indent + _format_final_exc_line(None, self._str, colorize=colorize) 1232 return 1233 1234 stype = self.exc_type_str 1235 if not self._is_syntax_error: 1236 if _depth > 0: 1237 # Nested exceptions needs correct handling of multiline messages. 1238 formatted = _format_final_exc_line( 1239 stype, self._str, insert_final_newline=False, colorize=colorize 1240 ).split('\n') 1241 yield from [ 1242 indent + l + '\n' 1243 for l in formatted 1244 ] 1245 else: 1246 yield _format_final_exc_line(stype, self._str, colorize=colorize) 1247 else: 1248 yield from [indent + l for l in self._format_syntax_error(stype, colorize=colorize)] 1249 1250 if ( 1251 isinstance(self.__notes__, collections.abc.Sequence) 1252 and not isinstance(self.__notes__, (str, bytes)) 1253 ): 1254 for note in self.__notes__: 1255 note = _safe_string(note, 'note') 1256 yield from [indent + l + '\n' for l in note.split('\n')] 1257 elif self.__notes__ is not None: 1258 yield indent + "{}\n".format(_safe_string(self.__notes__, '__notes__', func=repr)) 1259 1260 if self.exceptions and show_group: 1261 for ex in self.exceptions: 1262 yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1, colorize=colorize) 1263 1264 def _format_syntax_error(self, stype, **kwargs): 1265 """Format SyntaxError exceptions (internal helper).""" 1266 # Show exactly where the problem was found. 1267 colorize = kwargs.get("colorize", False) 1268 filename_suffix = '' 1269 if self.lineno is not None: 1270 if colorize: 1271 yield ' File {}"{}"{}, line {}{}{}\n'.format( 1272 ANSIColors.MAGENTA, 1273 self.filename or "<string>", 1274 ANSIColors.RESET, 1275 ANSIColors.MAGENTA, 1276 self.lineno, 1277 ANSIColors.RESET, 1278 ) 1279 else: 1280 yield ' File "{}", line {}\n'.format( 1281 self.filename or "<string>", self.lineno) 1282 elif self.filename is not None: 1283 filename_suffix = ' ({})'.format(self.filename) 1284 1285 text = self.text 1286 if text is not None: 1287 # text = " foo\n" 1288 # rtext = " foo" 1289 # ltext = "foo" 1290 rtext = text.rstrip('\n') 1291 ltext = rtext.lstrip(' \n\f') 1292 spaces = len(rtext) - len(ltext) 1293 if self.offset is None: 1294 yield ' {}\n'.format(ltext) 1295 else: 1296 offset = self.offset 1297 if self.lineno == self.end_lineno: 1298 end_offset = self.end_offset if self.end_offset not in {None, 0} else offset 1299 else: 1300 end_offset = len(rtext) + 1 1301 1302 if self.text and offset > len(self.text): 1303 offset = len(rtext) + 1 1304 if self.text and end_offset > len(self.text): 1305 end_offset = len(rtext) + 1 1306 if offset >= end_offset or end_offset < 0: 1307 end_offset = offset + 1 1308 1309 # Convert 1-based column offset to 0-based index into stripped text 1310 colno = offset - 1 - spaces 1311 end_colno = end_offset - 1 - spaces 1312 caretspace = ' ' 1313 if colno >= 0: 1314 # non-space whitespace (likes tabs) must be kept for alignment 1315 caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno]) 1316 start_color = end_color = "" 1317 if colorize: 1318 # colorize from colno to end_colno 1319 ltext = ( 1320 ltext[:colno] + 1321 ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET + 1322 ltext[end_colno:] 1323 ) 1324 start_color = ANSIColors.BOLD_RED 1325 end_color = ANSIColors.RESET 1326 yield ' {}\n'.format(ltext) 1327 yield ' {}{}{}{}\n'.format( 1328 "".join(caretspace), 1329 start_color, 1330 ('^' * (end_colno - colno)), 1331 end_color, 1332 ) 1333 else: 1334 yield ' {}\n'.format(ltext) 1335 msg = self.msg or "<no detail available>" 1336 if colorize: 1337 yield "{}{}{}: {}{}{}{}\n".format( 1338 ANSIColors.BOLD_MAGENTA, 1339 stype, 1340 ANSIColors.RESET, 1341 ANSIColors.MAGENTA, 1342 msg, 1343 ANSIColors.RESET, 1344 filename_suffix) 1345 else: 1346 yield "{}: {}{}\n".format(stype, msg, filename_suffix) 1347 1348 def format(self, *, chain=True, _ctx=None, **kwargs): 1349 """Format the exception. 1350 1351 If chain is not *True*, *__cause__* and *__context__* will not be formatted. 1352 1353 The return value is a generator of strings, each ending in a newline and 1354 some containing internal newlines. `print_exception` is a wrapper around 1355 this method which just prints the lines to a file. 1356 1357 The message indicating which exception occurred is always the last 1358 string in the output. 1359 """ 1360 colorize = kwargs.get("colorize", False) 1361 if _ctx is None: 1362 _ctx = _ExceptionPrintContext() 1363 1364 output = [] 1365 exc = self 1366 if chain: 1367 while exc: 1368 if exc.__cause__ is not None: 1369 chained_msg = _cause_message 1370 chained_exc = exc.__cause__ 1371 elif (exc.__context__ is not None and 1372 not exc.__suppress_context__): 1373 chained_msg = _context_message 1374 chained_exc = exc.__context__ 1375 else: 1376 chained_msg = None 1377 chained_exc = None 1378 1379 output.append((chained_msg, exc)) 1380 exc = chained_exc 1381 else: 1382 output.append((None, exc)) 1383 1384 for msg, exc in reversed(output): 1385 if msg is not None: 1386 yield from _ctx.emit(msg) 1387 if exc.exceptions is None: 1388 if exc.stack: 1389 yield from _ctx.emit('Traceback (most recent call last):\n') 1390 yield from _ctx.emit(exc.stack.format(colorize=colorize)) 1391 yield from _ctx.emit(exc.format_exception_only(colorize=colorize)) 1392 elif _ctx.exception_group_depth > self.max_group_depth: 1393 # exception group, but depth exceeds limit 1394 yield from _ctx.emit( 1395 f"... (max_group_depth is {self.max_group_depth})\n") 1396 else: 1397 # format exception group 1398 is_toplevel = (_ctx.exception_group_depth == 0) 1399 if is_toplevel: 1400 _ctx.exception_group_depth += 1 1401 1402 if exc.stack: 1403 yield from _ctx.emit( 1404 'Exception Group Traceback (most recent call last):\n', 1405 margin_char = '+' if is_toplevel else None) 1406 yield from _ctx.emit(exc.stack.format(colorize=colorize)) 1407 1408 yield from _ctx.emit(exc.format_exception_only(colorize=colorize)) 1409 num_excs = len(exc.exceptions) 1410 if num_excs <= self.max_group_width: 1411 n = num_excs 1412 else: 1413 n = self.max_group_width + 1 1414 _ctx.need_close = False 1415 for i in range(n): 1416 last_exc = (i == n-1) 1417 if last_exc: 1418 # The closing frame may be added by a recursive call 1419 _ctx.need_close = True 1420 1421 if self.max_group_width is not None: 1422 truncated = (i >= self.max_group_width) 1423 else: 1424 truncated = False 1425 title = f'{i+1}' if not truncated else '...' 1426 yield (_ctx.indent() + 1427 ('+-' if i==0 else ' ') + 1428 f'+---------------- {title} ----------------\n') 1429 _ctx.exception_group_depth += 1 1430 if not truncated: 1431 yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize) 1432 else: 1433 remaining = num_excs - self.max_group_width 1434 plural = 's' if remaining > 1 else '' 1435 yield from _ctx.emit( 1436 f"and {remaining} more exception{plural}\n") 1437 1438 if last_exc and _ctx.need_close: 1439 yield (_ctx.indent() + 1440 "+------------------------------------\n") 1441 _ctx.need_close = False 1442 _ctx.exception_group_depth -= 1 1443 1444 if is_toplevel: 1445 assert _ctx.exception_group_depth == 1 1446 _ctx.exception_group_depth = 0 1447 1448 1449 def print(self, *, file=None, chain=True, **kwargs): 1450 """Print the result of self.format(chain=chain) to 'file'.""" 1451 colorize = kwargs.get("colorize", False) 1452 if file is None: 1453 file = sys.stderr 1454 for line in self.format(chain=chain, colorize=colorize): 1455 print(line, file=file, end="") 1456 1457 1458_MAX_CANDIDATE_ITEMS = 750 1459_MAX_STRING_SIZE = 40 1460_MOVE_COST = 2 1461_CASE_COST = 1 1462 1463 1464def _substitution_cost(ch_a, ch_b): 1465 if ch_a == ch_b: 1466 return 0 1467 if ch_a.lower() == ch_b.lower(): 1468 return _CASE_COST 1469 return _MOVE_COST 1470 1471 1472def _compute_suggestion_error(exc_value, tb, wrong_name): 1473 if wrong_name is None or not isinstance(wrong_name, str): 1474 return None 1475 if isinstance(exc_value, AttributeError): 1476 obj = exc_value.obj 1477 try: 1478 d = dir(obj) 1479 hide_underscored = (wrong_name[:1] != '_') 1480 if hide_underscored and tb is not None: 1481 while tb.tb_next is not None: 1482 tb = tb.tb_next 1483 frame = tb.tb_frame 1484 if 'self' in frame.f_locals and frame.f_locals['self'] is obj: 1485 hide_underscored = False 1486 if hide_underscored: 1487 d = [x for x in d if x[:1] != '_'] 1488 except Exception: 1489 return None 1490 elif isinstance(exc_value, ImportError): 1491 try: 1492 mod = __import__(exc_value.name) 1493 d = dir(mod) 1494 if wrong_name[:1] != '_': 1495 d = [x for x in d if x[:1] != '_'] 1496 except Exception: 1497 return None 1498 else: 1499 assert isinstance(exc_value, NameError) 1500 # find most recent frame 1501 if tb is None: 1502 return None 1503 while tb.tb_next is not None: 1504 tb = tb.tb_next 1505 frame = tb.tb_frame 1506 d = ( 1507 list(frame.f_locals) 1508 + list(frame.f_globals) 1509 + list(frame.f_builtins) 1510 ) 1511 1512 # Check first if we are in a method and the instance 1513 # has the wrong name as attribute 1514 if 'self' in frame.f_locals: 1515 self = frame.f_locals['self'] 1516 if hasattr(self, wrong_name): 1517 return f"self.{wrong_name}" 1518 1519 try: 1520 import _suggestions 1521 except ImportError: 1522 pass 1523 else: 1524 return _suggestions._generate_suggestions(d, wrong_name) 1525 1526 # Compute closest match 1527 1528 if len(d) > _MAX_CANDIDATE_ITEMS: 1529 return None 1530 wrong_name_len = len(wrong_name) 1531 if wrong_name_len > _MAX_STRING_SIZE: 1532 return None 1533 best_distance = wrong_name_len 1534 suggestion = None 1535 for possible_name in d: 1536 if possible_name == wrong_name: 1537 # A missing attribute is "found". Don't suggest it (see GH-88821). 1538 continue 1539 # No more than 1/3 of the involved characters should need changed. 1540 max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6 1541 # Don't take matches we've already beaten. 1542 max_distance = min(max_distance, best_distance - 1) 1543 current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance) 1544 if current_distance > max_distance: 1545 continue 1546 if not suggestion or current_distance < best_distance: 1547 suggestion = possible_name 1548 best_distance = current_distance 1549 return suggestion 1550 1551 1552def _levenshtein_distance(a, b, max_cost): 1553 # A Python implementation of Python/suggestions.c:levenshtein_distance. 1554 1555 # Both strings are the same 1556 if a == b: 1557 return 0 1558 1559 # Trim away common affixes 1560 pre = 0 1561 while a[pre:] and b[pre:] and a[pre] == b[pre]: 1562 pre += 1 1563 a = a[pre:] 1564 b = b[pre:] 1565 post = 0 1566 while a[:post or None] and b[:post or None] and a[post-1] == b[post-1]: 1567 post -= 1 1568 a = a[:post or None] 1569 b = b[:post or None] 1570 if not a or not b: 1571 return _MOVE_COST * (len(a) + len(b)) 1572 if len(a) > _MAX_STRING_SIZE or len(b) > _MAX_STRING_SIZE: 1573 return max_cost + 1 1574 1575 # Prefer shorter buffer 1576 if len(b) < len(a): 1577 a, b = b, a 1578 1579 # Quick fail when a match is impossible 1580 if (len(b) - len(a)) * _MOVE_COST > max_cost: 1581 return max_cost + 1 1582 1583 # Instead of producing the whole traditional len(a)-by-len(b) 1584 # matrix, we can update just one row in place. 1585 # Initialize the buffer row 1586 row = list(range(_MOVE_COST, _MOVE_COST * (len(a) + 1), _MOVE_COST)) 1587 1588 result = 0 1589 for bindex in range(len(b)): 1590 bchar = b[bindex] 1591 distance = result = bindex * _MOVE_COST 1592 minimum = sys.maxsize 1593 for index in range(len(a)): 1594 # 1) Previous distance in this row is cost(b[:b_index], a[:index]) 1595 substitute = distance + _substitution_cost(bchar, a[index]) 1596 # 2) cost(b[:b_index], a[:index+1]) from previous row 1597 distance = row[index] 1598 # 3) existing result is cost(b[:b_index+1], a[index]) 1599 1600 insert_delete = min(result, distance) + _MOVE_COST 1601 result = min(insert_delete, substitute) 1602 1603 # cost(b[:b_index+1], a[:index+1]) 1604 row[index] = result 1605 if result < minimum: 1606 minimum = result 1607 if minimum > max_cost: 1608 # Everything in this row is too big, so bail early. 1609 return max_cost + 1 1610 return result 1611