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