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