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