• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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