Format: text/x-python
⬇ Download File (55.3 KB)
   1  # -*- coding: utf-8 -*-
   2  """
   3  Verbose and colourful traceback formatting.
   4  
   5  **ColorTB**
   6  
   7  I've always found it a bit hard to visually parse tracebacks in Python.  The
   8  ColorTB class is a solution to that problem.  It colors the different parts of a
   9  traceback in a manner similar to what you would expect from a syntax-highlighting
  10  text editor.
  11  
  12  Installation instructions for ColorTB::
  13  
  14      import sys,ultratb
  15      sys.excepthook = ultratb.ColorTB()
  16  
  17  **VerboseTB**
  18  
  19  I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
  20  of useful info when a traceback occurs.  Ping originally had it spit out HTML
  21  and intended it for CGI programmers, but why should they have all the fun?  I
  22  altered it to spit out colored text to the terminal.  It's a bit overwhelming,
  23  but kind of neat, and maybe useful for long-running programs that you believe
  24  are bug-free.  If a crash *does* occur in that type of program you want details.
  25  Give it a shot--you'll love it or you'll hate it.
  26  
  27  .. note::
  28  
  29    The Verbose mode prints the variables currently visible where the exception
  30    happened (shortening their strings if too long). This can potentially be
  31    very slow, if you happen to have a huge data structure whose string
  32    representation is complex to compute. Your computer may appear to freeze for
  33    a while with cpu usage at 100%. If this occurs, you can cancel the traceback
  34    with Ctrl-C (maybe hitting it more than once).
  35  
  36    If you encounter this kind of situation often, you may want to use the
  37    Verbose_novars mode instead of the regular Verbose, which avoids formatting
  38    variables (but otherwise includes the information and context given by
  39    Verbose).
  40  
  41  .. note::
  42  
  43    The verbose mode print all variables in the stack, which means it can
  44    potentially leak sensitive information like access keys, or unencrypted
  45    password.
  46  
  47  Installation instructions for VerboseTB::
  48  
  49      import sys,ultratb
  50      sys.excepthook = ultratb.VerboseTB()
  51  
  52  Note:  Much of the code in this module was lifted verbatim from the standard
  53  library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
  54  
  55  Color schemes
  56  -------------
  57  
  58  The colors are defined in the class TBTools through the use of the
  59  ColorSchemeTable class. Currently the following exist:
  60  
  61    - NoColor: allows all of this module to be used in any terminal (the color
  62      escapes are just dummy blank strings).
  63  
  64    - Linux: is meant to look good in a terminal like the Linux console (black
  65      or very dark background).
  66  
  67    - LightBG: similar to Linux but swaps dark/light colors to be more readable
  68      in light background terminals.
  69  
  70    - Neutral: a neutral color scheme that should be readable on both light and
  71      dark background
  72  
  73  You can implement other color schemes easily, the syntax is fairly
  74  self-explanatory. Please send back new schemes you develop to the author for
  75  possible inclusion in future releases.
  76  
  77  Inheritance diagram:
  78  
  79  .. inheritance-diagram:: IPython.core.ultratb
  80     :parts: 3
  81  """
  82  
  83  #*****************************************************************************
  84  # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
  85  # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
  86  #
  87  # Distributed under the terms of the BSD License.  The full license is in
  88  # the file COPYING, distributed as part of this software.
  89  #*****************************************************************************
  90  
  91  
  92  from collections.abc import Sequence
  93  import functools
  94  import inspect
  95  import linecache
  96  import pydoc
  97  import sys
  98  import time
  99  import traceback
 100  import types
 101  from types import TracebackType
 102  from typing import Any, List, Optional, Tuple
 103  
 104  import stack_data
 105  from pygments.formatters.terminal256 import Terminal256Formatter
 106  from pygments.styles import get_style_by_name
 107  
 108  import IPython.utils.colorable as colorable
 109  # IPython's own modules
 110  from IPython import get_ipython
 111  from IPython.core import debugger
 112  from IPython.core.display_trap import DisplayTrap
 113  from IPython.core.excolors import exception_colors
 114  from IPython.utils import PyColorize
 115  from IPython.utils import path as util_path
 116  from IPython.utils import py3compat
 117  from IPython.utils.terminal import get_terminal_size
 118  
 119  # Globals
 120  # amount of space to put line numbers before verbose tracebacks
 121  INDENT_SIZE = 8
 122  
 123  # Default color scheme.  This is used, for example, by the traceback
 124  # formatter.  When running in an actual IPython instance, the user's rc.colors
 125  # value is used, but having a module global makes this functionality available
 126  # to users of ultratb who are NOT running inside ipython.
 127  DEFAULT_SCHEME = 'NoColor'
 128  FAST_THRESHOLD = 10_000
 129  
 130  # ---------------------------------------------------------------------------
 131  # Code begins
 132  
 133  # Helper function -- largely belongs to VerboseTB, but we need the same
 134  # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
 135  # can be recognized properly by ipython.el's py-traceback-line-re
 136  # (SyntaxErrors have to be treated specially because they have no traceback)
 137  
 138  
 139  @functools.lru_cache()
 140  def count_lines_in_py_file(filename: str) -> int:
 141      """
 142      Given a filename, returns the number of lines in the file
 143      if it ends with the extension ".py". Otherwise, returns 0.
 144      """
 145      if not filename.endswith(".py"):
 146          return 0
 147      else:
 148          try:
 149              with open(filename, "r") as file:
 150                  s = sum(1 for line in file)
 151          except UnicodeError:
 152              return 0
 153      return s
 154  
 155      """
 156      Given a frame object, returns the total number of lines in the file
 157      if the filename ends with the extension ".py". Otherwise, returns 0.
 158      """
 159  
 160  
 161  def get_line_number_of_frame(frame: types.FrameType) -> int:
 162      """
 163      Given a frame object, returns the total number of lines in the file
 164      containing the frame's code object, or the number of lines in the
 165      frame's source code if the file is not available.
 166  
 167      Parameters
 168      ----------
 169      frame : FrameType
 170          The frame object whose line number is to be determined.
 171  
 172      Returns
 173      -------
 174      int
 175          The total number of lines in the file containing the frame's
 176          code object, or the number of lines in the frame's source code
 177          if the file is not available.
 178      """
 179      filename = frame.f_code.co_filename
 180      if filename is None:
 181          print("No file....")
 182          lines, first = inspect.getsourcelines(frame)
 183          return first + len(lines)
 184      return count_lines_in_py_file(filename)
 185  
 186  
 187  def _safe_string(value, what, func=str):
 188      # Copied from cpython/Lib/traceback.py
 189      try:
 190          return func(value)
 191      except:
 192          return f"<{what} {func.__name__}() failed>"
 193  
 194  
 195  def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
 196      """
 197      Format tracebacks lines with pointing arrow, leading numbers...
 198  
 199      Parameters
 200      ----------
 201      lines : list[Line]
 202      Colors
 203          ColorScheme used.
 204      lvals : str
 205          Values of local variables, already colored, to inject just after the error line.
 206      """
 207      numbers_width = INDENT_SIZE - 1
 208      res = []
 209  
 210      for stack_line in lines:
 211          if stack_line is stack_data.LINE_GAP:
 212              res.append('%s   (...)%s\n' % (Colors.linenoEm, Colors.Normal))
 213              continue
 214  
 215          line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
 216          lineno = stack_line.lineno
 217          if stack_line.is_current:
 218              # This is the line with the error
 219              pad = numbers_width - len(str(lineno))
 220              num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
 221              start_color = Colors.linenoEm
 222          else:
 223              num = '%*s' % (numbers_width, lineno)
 224              start_color = Colors.lineno
 225  
 226          line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
 227  
 228          res.append(line)
 229          if lvals and stack_line.is_current:
 230              res.append(lvals + '\n')
 231      return res
 232  
 233  def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
 234      """
 235      Format tracebacks lines with pointing arrow, leading numbers...
 236  
 237      Parameters
 238      ==========
 239  
 240      lnum: int
 241          number of the target line of code.
 242      index: int
 243          which line in the list should be highlighted.
 244      lines: list[string]
 245      Colors:
 246          ColorScheme used.
 247      lvals: bytes
 248          Values of local variables, already colored, to inject just after the error line.
 249      _line_format: f (str) -> (str, bool)
 250          return (colorized version of str, failure to do so)
 251      """
 252      numbers_width = INDENT_SIZE - 1
 253      res = []
 254      for i, line in enumerate(lines, lnum - index):
 255          # assert isinstance(line, str)
 256          line = py3compat.cast_unicode(line)
 257  
 258          new_line, err = _line_format(line, "str")
 259          if not err:
 260              line = new_line
 261  
 262          if i == lnum:
 263              # This is the line with the error
 264              pad = numbers_width - len(str(i))
 265              num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
 266              line = "%s%s%s %s%s" % (
 267                  Colors.linenoEm,
 268                  num,
 269                  Colors.line,
 270                  line,
 271                  Colors.Normal,
 272              )
 273          else:
 274              num = "%*s" % (numbers_width, i)
 275              line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
 276  
 277          res.append(line)
 278          if lvals and i == lnum:
 279              res.append(lvals + "\n")
 280      return res
 281  
 282  
 283  def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
 284      """
 285      Format filename lines with custom formatting from caching compiler or `File *.py` by default
 286  
 287      Parameters
 288      ----------
 289      file : str
 290      ColorFilename
 291          ColorScheme's filename coloring to be used.
 292      ColorNormal
 293          ColorScheme's normal coloring to be used.
 294      """
 295      ipinst = get_ipython()
 296      if (
 297          ipinst is not None
 298          and (data := ipinst.compile.format_code_name(file)) is not None
 299      ):
 300          label, name = data
 301          if lineno is None:
 302              tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
 303          else:
 304              tpl_link = (
 305                  f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
 306              )
 307      else:
 308          label = "File"
 309          name = util_path.compress_user(
 310              py3compat.cast_unicode(file, util_path.fs_encoding)
 311          )
 312          if lineno is None:
 313              tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
 314          else:
 315              # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
 316              tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
 317  
 318      return tpl_link.format(label=label, name=name, lineno=lineno)
 319  
 320  #---------------------------------------------------------------------------
 321  # Module classes
 322  class TBTools(colorable.Colorable):
 323      """Basic tools used by all traceback printer classes."""
 324  
 325      # Number of frames to skip when reporting tracebacks
 326      tb_offset = 0
 327  
 328      def __init__(
 329          self,
 330          color_scheme="NoColor",
 331          call_pdb=False,
 332          ostream=None,
 333          parent=None,
 334          config=None,
 335          *,
 336          debugger_cls=None,
 337      ):
 338          # Whether to call the interactive pdb debugger after printing
 339          # tracebacks or not
 340          super(TBTools, self).__init__(parent=parent, config=config)
 341          self.call_pdb = call_pdb
 342  
 343          # Output stream to write to.  Note that we store the original value in
 344          # a private attribute and then make the public ostream a property, so
 345          # that we can delay accessing sys.stdout until runtime.  The way
 346          # things are written now, the sys.stdout object is dynamically managed
 347          # so a reference to it should NEVER be stored statically.  This
 348          # property approach confines this detail to a single location, and all
 349          # subclasses can simply access self.ostream for writing.
 350          self._ostream = ostream
 351  
 352          # Create color table
 353          self.color_scheme_table = exception_colors()
 354  
 355          self.set_colors(color_scheme)
 356          self.old_scheme = color_scheme  # save initial value for toggles
 357          self.debugger_cls = debugger_cls or debugger.Pdb
 358  
 359          if call_pdb:
 360              self.pdb = self.debugger_cls()
 361          else:
 362              self.pdb = None
 363  
 364      def _get_ostream(self):
 365          """Output stream that exceptions are written to.
 366  
 367          Valid values are:
 368  
 369          - None: the default, which means that IPython will dynamically resolve
 370            to sys.stdout.  This ensures compatibility with most tools, including
 371            Windows (where plain stdout doesn't recognize ANSI escapes).
 372  
 373          - Any object with 'write' and 'flush' attributes.
 374          """
 375          return sys.stdout if self._ostream is None else self._ostream
 376  
 377      def _set_ostream(self, val):
 378          assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
 379          self._ostream = val
 380  
 381      ostream = property(_get_ostream, _set_ostream)
 382  
 383      @staticmethod
 384      def _get_chained_exception(exception_value):
 385          cause = getattr(exception_value, "__cause__", None)
 386          if cause:
 387              return cause
 388          if getattr(exception_value, "__suppress_context__", False):
 389              return None
 390          return getattr(exception_value, "__context__", None)
 391  
 392      def get_parts_of_chained_exception(
 393          self, evalue
 394      ) -> Optional[Tuple[type, BaseException, TracebackType]]:
 395          chained_evalue = self._get_chained_exception(evalue)
 396  
 397          if chained_evalue:
 398              return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
 399          return None
 400  
 401      def prepare_chained_exception_message(self, cause) -> List[Any]:
 402          direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
 403          exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
 404  
 405          if cause:
 406              message = [[direct_cause]]
 407          else:
 408              message = [[exception_during_handling]]
 409          return message
 410  
 411      @property
 412      def has_colors(self) -> bool:
 413          return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
 414  
 415      def set_colors(self, *args, **kw):
 416          """Shorthand access to the color table scheme selector method."""
 417  
 418          # Set own color table
 419          self.color_scheme_table.set_active_scheme(*args, **kw)
 420          # for convenience, set Colors to the active scheme
 421          self.Colors = self.color_scheme_table.active_colors
 422          # Also set colors of debugger
 423          if hasattr(self, 'pdb') and self.pdb is not None:
 424              self.pdb.set_colors(*args, **kw)
 425  
 426      def color_toggle(self):
 427          """Toggle between the currently active color scheme and NoColor."""
 428  
 429          if self.color_scheme_table.active_scheme_name == 'NoColor':
 430              self.color_scheme_table.set_active_scheme(self.old_scheme)
 431              self.Colors = self.color_scheme_table.active_colors
 432          else:
 433              self.old_scheme = self.color_scheme_table.active_scheme_name
 434              self.color_scheme_table.set_active_scheme('NoColor')
 435              self.Colors = self.color_scheme_table.active_colors
 436  
 437      def stb2text(self, stb):
 438          """Convert a structured traceback (a list) to a string."""
 439          return '\n'.join(stb)
 440  
 441      def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
 442          """Return formatted traceback.
 443  
 444          Subclasses may override this if they add extra arguments.
 445          """
 446          tb_list = self.structured_traceback(etype, value, tb,
 447                                              tb_offset, context)
 448          return self.stb2text(tb_list)
 449  
 450      def structured_traceback(
 451          self,
 452          etype: type,
 453          evalue: Optional[BaseException],
 454          etb: Optional[TracebackType] = None,
 455          tb_offset: Optional[int] = None,
 456          number_of_lines_of_context: int = 5,
 457      ):
 458          """Return a list of traceback frames.
 459  
 460          Must be implemented by each class.
 461          """
 462          raise NotImplementedError()
 463  
 464  
 465  #---------------------------------------------------------------------------
 466  class ListTB(TBTools):
 467      """Print traceback information from a traceback list, with optional color.
 468  
 469      Calling requires 3 arguments: (etype, evalue, elist)
 470      as would be obtained by::
 471  
 472        etype, evalue, tb = sys.exc_info()
 473        if tb:
 474          elist = traceback.extract_tb(tb)
 475        else:
 476          elist = None
 477  
 478      It can thus be used by programs which need to process the traceback before
 479      printing (such as console replacements based on the code module from the
 480      standard library).
 481  
 482      Because they are meant to be called without a full traceback (only a
 483      list), instances of this class can't call the interactive pdb debugger."""
 484  
 485  
 486      def __call__(self, etype, value, elist):
 487          self.ostream.flush()
 488          self.ostream.write(self.text(etype, value, elist))
 489          self.ostream.write('\n')
 490  
 491      def _extract_tb(self, tb):
 492          if tb:
 493              return traceback.extract_tb(tb)
 494          else:
 495              return None
 496  
 497      def structured_traceback(
 498          self,
 499          etype: type,
 500          evalue: Optional[BaseException],
 501          etb: Optional[TracebackType] = None,
 502          tb_offset: Optional[int] = None,
 503          context=5,
 504      ):
 505          """Return a color formatted string with the traceback info.
 506  
 507          Parameters
 508          ----------
 509          etype : exception type
 510              Type of the exception raised.
 511          evalue : object
 512              Data stored in the exception
 513          etb : list | TracebackType | None
 514              If list: List of frames, see class docstring for details.
 515              If Traceback: Traceback of the exception.
 516          tb_offset : int, optional
 517              Number of frames in the traceback to skip.  If not given, the
 518              instance evalue is used (set in constructor).
 519          context : int, optional
 520              Number of lines of context information to print.
 521  
 522          Returns
 523          -------
 524          String with formatted exception.
 525          """
 526          # This is a workaround to get chained_exc_ids in recursive calls
 527          # etb should not be a tuple if structured_traceback is not recursive
 528          if isinstance(etb, tuple):
 529              etb, chained_exc_ids = etb
 530          else:
 531              chained_exc_ids = set()
 532  
 533          if isinstance(etb, list):
 534              elist = etb
 535          elif etb is not None:
 536              elist = self._extract_tb(etb)
 537          else:
 538              elist = []
 539          tb_offset = self.tb_offset if tb_offset is None else tb_offset
 540          assert isinstance(tb_offset, int)
 541          Colors = self.Colors
 542          out_list = []
 543          if elist:
 544  
 545              if tb_offset and len(elist) > tb_offset:
 546                  elist = elist[tb_offset:]
 547  
 548              out_list.append('Traceback %s(most recent call last)%s:' %
 549                              (Colors.normalEm, Colors.Normal) + '\n')
 550              out_list.extend(self._format_list(elist))
 551          # The exception info should be a single entry in the list.
 552          lines = ''.join(self._format_exception_only(etype, evalue))
 553          out_list.append(lines)
 554  
 555          # Find chained exceptions if we have a traceback (not for exception-only mode)
 556          if etb is not None:
 557              exception = self.get_parts_of_chained_exception(evalue)
 558  
 559              if exception and (id(exception[1]) not in chained_exc_ids):
 560                  chained_exception_message = (
 561                      self.prepare_chained_exception_message(evalue.__cause__)[0]
 562                      if evalue is not None
 563                      else ""
 564                  )
 565                  etype, evalue, etb = exception
 566                  # Trace exception to avoid infinite 'cause' loop
 567                  chained_exc_ids.add(id(exception[1]))
 568                  chained_exceptions_tb_offset = 0
 569                  out_list = (
 570                      self.structured_traceback(
 571                          etype,
 572                          evalue,
 573                          (etb, chained_exc_ids),  # type: ignore
 574                          chained_exceptions_tb_offset,
 575                          context,
 576                      )
 577                      + chained_exception_message
 578                      + out_list
 579                  )
 580  
 581          return out_list
 582  
 583      def _format_list(self, extracted_list):
 584          """Format a list of traceback entry tuples for printing.
 585  
 586          Given a list of tuples as returned by extract_tb() or
 587          extract_stack(), return a list of strings ready for printing.
 588          Each string in the resulting list corresponds to the item with the
 589          same index in the argument list.  Each string ends in a newline;
 590          the strings may contain internal newlines as well, for those items
 591          whose source text line is not None.
 592  
 593          Lifted almost verbatim from traceback.py
 594          """
 595  
 596          Colors = self.Colors
 597          output_list = []
 598          for ind, (filename, lineno, name, line) in enumerate(extracted_list):
 599              normalCol, nameCol, fileCol, lineCol = (
 600                  # Emphasize the last entry
 601                  (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
 602                  if ind == len(extracted_list) - 1
 603                  else (Colors.Normal, Colors.name, Colors.filename, "")
 604              )
 605  
 606              fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
 607              item = f"{normalCol}  {fns}"
 608  
 609              if name != "<module>":
 610                  item += f" in {nameCol}{name}{normalCol}\n"
 611              else:
 612                  item += "\n"
 613              if line:
 614                  item += f"{lineCol}    {line.strip()}{normalCol}\n"
 615              output_list.append(item)
 616  
 617          return output_list
 618  
 619      def _format_exception_only(self, etype, value):
 620          """Format the exception part of a traceback.
 621  
 622          The arguments are the exception type and value such as given by
 623          sys.exc_info()[:2]. The return value is a list of strings, each ending
 624          in a newline.  Normally, the list contains a single string; however,
 625          for SyntaxError exceptions, it contains several lines that (when
 626          printed) display detailed information about where the syntax error
 627          occurred.  The message indicating which exception occurred is the
 628          always last string in the list.
 629  
 630          Also lifted nearly verbatim from traceback.py
 631          """
 632          have_filedata = False
 633          Colors = self.Colors
 634          output_list = []
 635          stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
 636          if value is None:
 637              # Not sure if this can still happen in Python 2.6 and above
 638              output_list.append(stype + "\n")
 639          else:
 640              if issubclass(etype, SyntaxError):
 641                  have_filedata = True
 642                  if not value.filename: value.filename = "<string>"
 643                  if value.lineno:
 644                      lineno = value.lineno
 645                      textline = linecache.getline(value.filename, value.lineno)
 646                  else:
 647                      lineno = "unknown"
 648                      textline = ""
 649                  output_list.append(
 650                      "%s  %s%s\n"
 651                      % (
 652                          Colors.normalEm,
 653                          _format_filename(
 654                              value.filename,
 655                              Colors.filenameEm,
 656                              Colors.normalEm,
 657                              lineno=(None if lineno == "unknown" else lineno),
 658                          ),
 659                          Colors.Normal,
 660                      )
 661                  )
 662                  if textline == "":
 663                      textline = py3compat.cast_unicode(value.text, "utf-8")
 664  
 665                  if textline is not None:
 666                      i = 0
 667                      while i < len(textline) and textline[i].isspace():
 668                          i += 1
 669                      output_list.append(
 670                          "%s    %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
 671                      )
 672                      if value.offset is not None:
 673                          s = '    '
 674                          for c in textline[i:value.offset - 1]:
 675                              if c.isspace():
 676                                  s += c
 677                              else:
 678                                  s += " "
 679                          output_list.append(
 680                              "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
 681                          )
 682  
 683              try:
 684                  s = value.msg
 685              except Exception:
 686                  s = self._some_str(value)
 687              if s:
 688                  output_list.append(
 689                      "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
 690                  )
 691              else:
 692                  output_list.append("%s\n" % stype)
 693  
 694              # PEP-678 notes
 695              output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
 696  
 697          # sync with user hooks
 698          if have_filedata:
 699              ipinst = get_ipython()
 700              if ipinst is not None:
 701                  ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
 702  
 703          return output_list
 704  
 705      def get_exception_only(self, etype, value):
 706          """Only print the exception type and message, without a traceback.
 707  
 708          Parameters
 709          ----------
 710          etype : exception type
 711          value : exception value
 712          """
 713          return ListTB.structured_traceback(self, etype, value)
 714  
 715      def show_exception_only(self, etype, evalue):
 716          """Only print the exception type and message, without a traceback.
 717  
 718          Parameters
 719          ----------
 720          etype : exception type
 721          evalue : exception value
 722          """
 723          # This method needs to use __call__ from *this* class, not the one from
 724          # a subclass whose signature or behavior may be different
 725          ostream = self.ostream
 726          ostream.flush()
 727          ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
 728          ostream.flush()
 729  
 730      def _some_str(self, value):
 731          # Lifted from traceback.py
 732          try:
 733              return py3compat.cast_unicode(str(value))
 734          except:
 735              return u'<unprintable %s object>' % type(value).__name__
 736  
 737  
 738  class FrameInfo:
 739      """
 740      Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
 741      really long frames.
 742      """
 743  
 744      description: Optional[str]
 745      filename: Optional[str]
 746      lineno: Tuple[int]
 747      # number of context lines to use
 748      context: Optional[int]
 749      raw_lines: List[str]
 750  
 751      @classmethod
 752      def _from_stack_data_FrameInfo(cls, frame_info):
 753          return cls(
 754              getattr(frame_info, "description", None),
 755              getattr(frame_info, "filename", None),  # type: ignore[arg-type]
 756              getattr(frame_info, "lineno", None),  # type: ignore[arg-type]
 757              getattr(frame_info, "frame", None),
 758              getattr(frame_info, "code", None),
 759              sd=frame_info,
 760              context=None,
 761          )
 762  
 763      def __init__(
 764          self,
 765          description: Optional[str],
 766          filename: str,
 767          lineno: Tuple[int],
 768          frame,
 769          code,
 770          *,
 771          sd=None,
 772          context=None,
 773      ):
 774          self.description = description
 775          self.filename = filename
 776          self.lineno = lineno
 777          self.frame = frame
 778          self.code = code
 779          self._sd = sd
 780          self.context = context
 781  
 782          # self.lines = []
 783          if sd is None:
 784              try:
 785                  # return a list of source lines and a starting line number
 786                  self.raw_lines = inspect.getsourcelines(frame)[0]
 787              except OSError:
 788                  self.raw_lines = [
 789                      "'Could not get source, probably due dynamically evaluated source code.'"
 790                  ]
 791  
 792      @property
 793      def variables_in_executing_piece(self):
 794          if self._sd:
 795              return self._sd.variables_in_executing_piece
 796          else:
 797              return []
 798  
 799      @property
 800      def lines(self):
 801          from executing.executing import NotOneValueFound
 802  
 803          try:
 804              return self._sd.lines
 805          except NotOneValueFound:
 806  
 807              class Dummy:
 808                  lineno = 0
 809                  is_current = False
 810  
 811                  def render(self, *, pygmented):
 812                      return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
 813  
 814              return [Dummy()]
 815  
 816      @property
 817      def executing(self):
 818          if self._sd:
 819              return self._sd.executing
 820          else:
 821              return None
 822  
 823  
 824  # ----------------------------------------------------------------------------
 825  class VerboseTB(TBTools):
 826      """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
 827      of HTML.  Requires inspect and pydoc.  Crazy, man.
 828  
 829      Modified version which optionally strips the topmost entries from the
 830      traceback, to be used with alternate interpreters (because their own code
 831      would appear in the traceback)."""
 832  
 833      tb_highlight = "bg:ansiyellow"
 834      tb_highlight_style = "default"
 835  
 836      def __init__(
 837          self,
 838          color_scheme: str = "Linux",
 839          call_pdb: bool = False,
 840          ostream=None,
 841          tb_offset: int = 0,
 842          long_header: bool = False,
 843          include_vars: bool = True,
 844          check_cache=None,
 845          debugger_cls=None,
 846          parent=None,
 847          config=None,
 848      ):
 849          """Specify traceback offset, headers and color scheme.
 850  
 851          Define how many frames to drop from the tracebacks. Calling it with
 852          tb_offset=1 allows use of this handler in interpreters which will have
 853          their own code at the top of the traceback (VerboseTB will first
 854          remove that frame before printing the traceback info)."""
 855          TBTools.__init__(
 856              self,
 857              color_scheme=color_scheme,
 858              call_pdb=call_pdb,
 859              ostream=ostream,
 860              parent=parent,
 861              config=config,
 862              debugger_cls=debugger_cls,
 863          )
 864          self.tb_offset = tb_offset
 865          self.long_header = long_header
 866          self.include_vars = include_vars
 867          # By default we use linecache.checkcache, but the user can provide a
 868          # different check_cache implementation.  This was formerly used by the
 869          # IPython kernel for interactive code, but is no longer necessary.
 870          if check_cache is None:
 871              check_cache = linecache.checkcache
 872          self.check_cache = check_cache
 873  
 874          self.skip_hidden = True
 875  
 876      def format_record(self, frame_info: FrameInfo):
 877          """Format a single stack frame"""
 878          assert isinstance(frame_info, FrameInfo)
 879          Colors = self.Colors  # just a shorthand + quicker name lookup
 880          ColorsNormal = Colors.Normal  # used a lot
 881  
 882          if isinstance(frame_info._sd, stack_data.RepeatedFrames):
 883              return '    %s[... skipping similar frames: %s]%s\n' % (
 884                  Colors.excName, frame_info.description, ColorsNormal)
 885  
 886          indent = " " * INDENT_SIZE
 887          em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
 888          tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
 889          tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
 890              Colors.vName,
 891              Colors.valEm,
 892              ColorsNormal,
 893          )
 894          tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
 895  
 896          link = _format_filename(
 897              frame_info.filename,
 898              Colors.filenameEm,
 899              ColorsNormal,
 900              lineno=frame_info.lineno,
 901          )
 902          args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
 903          if frame_info.executing is not None:
 904              func = frame_info.executing.code_qualname()
 905          else:
 906              func = "?"
 907          if func == "<module>":
 908              call = ""
 909          else:
 910              # Decide whether to include variable details or not
 911              var_repr = eqrepr if self.include_vars else nullrepr
 912              try:
 913                  scope = inspect.formatargvalues(
 914                      args, varargs, varkw, locals_, formatvalue=var_repr
 915                  )
 916                  call = tpl_call.format(file=func, scope=scope)
 917              except KeyError:
 918                  # This happens in situations like errors inside generator
 919                  # expressions, where local variables are listed in the
 920                  # line, but can't be extracted from the frame.  I'm not
 921                  # 100% sure this isn't actually a bug in inspect itself,
 922                  # but since there's no info for us to compute with, the
 923                  # best we can do is report the failure and move on.  Here
 924                  # we must *not* call any traceback construction again,
 925                  # because that would mess up use of %debug later on.  So we
 926                  # simply report the failure and move on.  The only
 927                  # limitation will be that this frame won't have locals
 928                  # listed in the call signature.  Quite subtle problem...
 929                  # I can't think of a good way to validate this in a unit
 930                  # test, but running a script consisting of:
 931                  #  dict( (k,v.strip()) for (k,v) in range(10) )
 932                  # will illustrate the error, if this exception catch is
 933                  # disabled.
 934                  call = tpl_call_fail % func
 935  
 936          lvals = ''
 937          lvals_list = []
 938          if self.include_vars:
 939              try:
 940                  # we likely want to fix stackdata at some point, but
 941                  # still need a workaround.
 942                  fibp = frame_info.variables_in_executing_piece
 943                  for var in fibp:
 944                      lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
 945              except Exception:
 946                  lvals_list.append(
 947                      "Exception trying to inspect frame. No more locals available."
 948                  )
 949          if lvals_list:
 950              lvals = '%s%s' % (indent, em_normal.join(lvals_list))
 951  
 952          result = f'{link}{", " if call else ""}{call}\n'
 953          if frame_info._sd is None:
 954              # fast fallback if file is too long
 955              tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
 956              link = tpl_link % util_path.compress_user(frame_info.filename)
 957              level = "%s %s\n" % (link, call)
 958              _line_format = PyColorize.Parser(
 959                  style=self.color_scheme_table.active_scheme_name, parent=self
 960              ).format2
 961              first_line = frame_info.code.co_firstlineno
 962              current_line = frame_info.lineno[0]
 963              raw_lines = frame_info.raw_lines
 964              index = current_line - first_line
 965  
 966              if index >= frame_info.context:
 967                  start = max(index - frame_info.context, 0)
 968                  stop = index + frame_info.context
 969                  index = frame_info.context
 970              else:
 971                  start = 0
 972                  stop = index + frame_info.context
 973              raw_lines = raw_lines[start:stop]
 974  
 975              return "%s%s" % (
 976                  level,
 977                  "".join(
 978                      _simple_format_traceback_lines(
 979                          current_line,
 980                          index,
 981                          raw_lines,
 982                          Colors,
 983                          lvals,
 984                          _line_format,
 985                      )
 986                  ),
 987              )
 988              # result += "\n".join(frame_info.raw_lines)
 989          else:
 990              result += "".join(
 991                  _format_traceback_lines(
 992                      frame_info.lines, Colors, self.has_colors, lvals
 993                  )
 994              )
 995          return result
 996  
 997      def prepare_header(self, etype: str, long_version: bool = False):
 998          colors = self.Colors  # just a shorthand + quicker name lookup
 999          colorsnormal = colors.Normal  # used a lot
1000          exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
1001          width = min(75, get_terminal_size()[0])
1002          if long_version:
1003              # Header with the exception type, python version, and date
1004              pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
1005              date = time.ctime(time.time())
1006  
1007              head = "%s%s%s\n%s%s%s\n%s" % (
1008                  colors.topline,
1009                  "-" * width,
1010                  colorsnormal,
1011                  exc,
1012                  " " * (width - len(etype) - len(pyver)),
1013                  pyver,
1014                  date.rjust(width),
1015              )
1016              head += (
1017                  "\nA problem occurred executing Python code.  Here is the sequence of function"
1018                  "\ncalls leading up to the error, with the most recent (innermost) call last."
1019              )
1020          else:
1021              # Simplified header
1022              head = "%s%s" % (
1023                  exc,
1024                  "Traceback (most recent call last)".rjust(width - len(etype)),
1025              )
1026  
1027          return head
1028  
1029      def format_exception(self, etype, evalue):
1030          colors = self.Colors  # just a shorthand + quicker name lookup
1031          colorsnormal = colors.Normal  # used a lot
1032          # Get (safely) a string form of the exception info
1033          try:
1034              etype_str, evalue_str = map(str, (etype, evalue))
1035          except:
1036              # User exception is improperly defined.
1037              etype, evalue = str, sys.exc_info()[:2]
1038              etype_str, evalue_str = map(str, (etype, evalue))
1039  
1040          # PEP-678 notes
1041          notes = getattr(evalue, "__notes__", [])
1042          if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1043              notes = [_safe_string(notes, "__notes__", func=repr)]
1044  
1045          # ... and format it
1046          return [
1047              "{}{}{}: {}".format(
1048                  colors.excName,
1049                  etype_str,
1050                  colorsnormal,
1051                  py3compat.cast_unicode(evalue_str),
1052              ),
1053              *(
1054                  "{}{}".format(
1055                      colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1056                  )
1057                  for n in notes
1058              ),
1059          ]
1060  
1061      def format_exception_as_a_whole(
1062          self,
1063          etype: type,
1064          evalue: Optional[BaseException],
1065          etb: Optional[TracebackType],
1066          number_of_lines_of_context,
1067          tb_offset: Optional[int],
1068      ):
1069          """Formats the header, traceback and exception message for a single exception.
1070  
1071          This may be called multiple times by Python 3 exception chaining
1072          (PEP 3134).
1073          """
1074          # some locals
1075          orig_etype = etype
1076          try:
1077              etype = etype.__name__  # type: ignore
1078          except AttributeError:
1079              pass
1080  
1081          tb_offset = self.tb_offset if tb_offset is None else tb_offset
1082          assert isinstance(tb_offset, int)
1083          head = self.prepare_header(str(etype), self.long_header)
1084          records = (
1085              self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1086          )
1087  
1088          frames = []
1089          skipped = 0
1090          lastrecord = len(records) - 1
1091          for i, record in enumerate(records):
1092              if (
1093                  not isinstance(record._sd, stack_data.RepeatedFrames)
1094                  and self.skip_hidden
1095              ):
1096                  if (
1097                      record.frame.f_locals.get("__tracebackhide__", 0)
1098                      and i != lastrecord
1099                  ):
1100                      skipped += 1
1101                      continue
1102              if skipped:
1103                  Colors = self.Colors  # just a shorthand + quicker name lookup
1104                  ColorsNormal = Colors.Normal  # used a lot
1105                  frames.append(
1106                      "    %s[... skipping hidden %s frame]%s\n"
1107                      % (Colors.excName, skipped, ColorsNormal)
1108                  )
1109                  skipped = 0
1110              frames.append(self.format_record(record))
1111          if skipped:
1112              Colors = self.Colors  # just a shorthand + quicker name lookup
1113              ColorsNormal = Colors.Normal  # used a lot
1114              frames.append(
1115                  "    %s[... skipping hidden %s frame]%s\n"
1116                  % (Colors.excName, skipped, ColorsNormal)
1117              )
1118  
1119          formatted_exception = self.format_exception(etype, evalue)
1120          if records:
1121              frame_info = records[-1]
1122              ipinst = get_ipython()
1123              if ipinst is not None:
1124                  ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1125  
1126          return [[head] + frames + formatted_exception]
1127  
1128      def get_records(
1129          self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1130      ):
1131          assert etb is not None
1132          context = number_of_lines_of_context - 1
1133          after = context // 2
1134          before = context - after
1135          if self.has_colors:
1136              style = get_style_by_name(self.tb_highlight_style)
1137              style = stack_data.style_with_executing_node(style, self.tb_highlight)
1138              formatter = Terminal256Formatter(style=style)
1139          else:
1140              formatter = None
1141          options = stack_data.Options(
1142              before=before,
1143              after=after,
1144              pygments_formatter=formatter,
1145          )
1146  
1147          # Let's estimate the amount of code we will have to parse/highlight.
1148          cf: Optional[TracebackType] = etb
1149          max_len = 0
1150          tbs = []
1151          while cf is not None:
1152              try:
1153                  mod = inspect.getmodule(cf.tb_frame)
1154                  if mod is not None:
1155                      mod_name = mod.__name__
1156                      root_name, *_ = mod_name.split(".")
1157                      if root_name == "IPython":
1158                          cf = cf.tb_next
1159                          continue
1160                  max_len = get_line_number_of_frame(cf.tb_frame)
1161  
1162              except OSError:
1163                  max_len = 0
1164              max_len = max(max_len, max_len)
1165              tbs.append(cf)
1166              cf = getattr(cf, "tb_next", None)
1167  
1168          if max_len > FAST_THRESHOLD:
1169              FIs = []
1170              for tb in tbs:
1171                  frame = tb.tb_frame  # type: ignore
1172                  lineno = (frame.f_lineno,)
1173                  code = frame.f_code
1174                  filename = code.co_filename
1175                  # TODO: Here we need to use before/after/
1176                  FIs.append(
1177                      FrameInfo(
1178                          "Raw frame", filename, lineno, frame, code, context=context
1179                      )
1180                  )
1181              return FIs
1182          res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1183          res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1184          return res
1185  
1186      def structured_traceback(
1187          self,
1188          etype: type,
1189          evalue: Optional[BaseException],
1190          etb: Optional[TracebackType] = None,
1191          tb_offset: Optional[int] = None,
1192          number_of_lines_of_context: int = 5,
1193      ):
1194          """Return a nice text document describing the traceback."""
1195          formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1196                                                                 tb_offset)
1197  
1198          colors = self.Colors  # just a shorthand + quicker name lookup
1199          colorsnormal = colors.Normal  # used a lot
1200          head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1201          structured_traceback_parts = [head]
1202          chained_exceptions_tb_offset = 0
1203          lines_of_context = 3
1204          formatted_exceptions = formatted_exception
1205          exception = self.get_parts_of_chained_exception(evalue)
1206          if exception:
1207              assert evalue is not None
1208              formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1209              etype, evalue, etb = exception
1210          else:
1211              evalue = None
1212          chained_exc_ids = set()
1213          while evalue:
1214              formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1215                                                                       chained_exceptions_tb_offset)
1216              exception = self.get_parts_of_chained_exception(evalue)
1217  
1218              if exception and not id(exception[1]) in chained_exc_ids:
1219                  chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1220                  formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1221                  etype, evalue, etb = exception
1222              else:
1223                  evalue = None
1224  
1225          # we want to see exceptions in a reversed order:
1226          # the first exception should be on top
1227          for formatted_exception in reversed(formatted_exceptions):
1228              structured_traceback_parts += formatted_exception
1229  
1230          return structured_traceback_parts
1231  
1232      def debugger(self, force: bool = False):
1233          """Call up the pdb debugger if desired, always clean up the tb
1234          reference.
1235  
1236          Keywords:
1237  
1238            - force(False): by default, this routine checks the instance call_pdb
1239              flag and does not actually invoke the debugger if the flag is false.
1240              The 'force' option forces the debugger to activate even if the flag
1241              is false.
1242  
1243          If the call_pdb flag is set, the pdb interactive debugger is
1244          invoked. In all cases, the self.tb reference to the current traceback
1245          is deleted to prevent lingering references which hamper memory
1246          management.
1247  
1248          Note that each call to pdb() does an 'import readline', so if your app
1249          requires a special setup for the readline completers, you'll have to
1250          fix that by hand after invoking the exception handler."""
1251  
1252          if force or self.call_pdb:
1253              if self.pdb is None:
1254                  self.pdb = self.debugger_cls()
1255              # the system displayhook may have changed, restore the original
1256              # for pdb
1257              display_trap = DisplayTrap(hook=sys.__displayhook__)
1258              with display_trap:
1259                  self.pdb.reset()
1260                  # Find the right frame so we don't pop up inside ipython itself
1261                  if hasattr(self, "tb") and self.tb is not None:  # type: ignore[has-type]
1262                      etb = self.tb  # type: ignore[has-type]
1263                  else:
1264                      etb = self.tb = sys.last_traceback
1265                  while self.tb is not None and self.tb.tb_next is not None:
1266                      assert self.tb.tb_next is not None
1267                      self.tb = self.tb.tb_next
1268                  if etb and etb.tb_next:
1269                      etb = etb.tb_next
1270                  self.pdb.botframe = etb.tb_frame
1271                  # last_value should be deprecated, but last-exc sometimme not set
1272                  # please check why later and remove the getattr.
1273                  exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value)  # type: ignore[attr-defined]
1274                  if exc:
1275                      self.pdb.interaction(None, exc)
1276                  else:
1277                      self.pdb.interaction(None, etb)
1278  
1279          if hasattr(self, 'tb'):
1280              del self.tb
1281  
1282      def handler(self, info=None):
1283          (etype, evalue, etb) = info or sys.exc_info()
1284          self.tb = etb
1285          ostream = self.ostream
1286          ostream.flush()
1287          ostream.write(self.text(etype, evalue, etb))
1288          ostream.write('\n')
1289          ostream.flush()
1290  
1291      # Changed so an instance can just be called as VerboseTB_inst() and print
1292      # out the right info on its own.
1293      def __call__(self, etype=None, evalue=None, etb=None):
1294          """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1295          if etb is None:
1296              self.handler()
1297          else:
1298              self.handler((etype, evalue, etb))
1299          try:
1300              self.debugger()
1301          except KeyboardInterrupt:
1302              print("\nKeyboardInterrupt")
1303  
1304  
1305  #----------------------------------------------------------------------------
1306  class FormattedTB(VerboseTB, ListTB):
1307      """Subclass ListTB but allow calling with a traceback.
1308  
1309      It can thus be used as a sys.excepthook for Python > 2.1.
1310  
1311      Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1312  
1313      Allows a tb_offset to be specified. This is useful for situations where
1314      one needs to remove a number of topmost frames from the traceback (such as
1315      occurs with python programs that themselves execute other python code,
1316      like Python shells).  """
1317  
1318      mode: str
1319  
1320      def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1321                   ostream=None,
1322                   tb_offset=0, long_header=False, include_vars=False,
1323                   check_cache=None, debugger_cls=None,
1324                   parent=None, config=None):
1325  
1326          # NEVER change the order of this list. Put new modes at the end:
1327          self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1328          self.verbose_modes = self.valid_modes[1:3]
1329  
1330          VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1331                             ostream=ostream, tb_offset=tb_offset,
1332                             long_header=long_header, include_vars=include_vars,
1333                             check_cache=check_cache, debugger_cls=debugger_cls,
1334                             parent=parent, config=config)
1335  
1336          # Different types of tracebacks are joined with different separators to
1337          # form a single string.  They are taken from this dict
1338          self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1339                                  Minimal='')
1340          # set_mode also sets the tb_join_char attribute
1341          self.set_mode(mode)
1342  
1343      def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1344          tb_offset = self.tb_offset if tb_offset is None else tb_offset
1345          mode = self.mode
1346          if mode in self.verbose_modes:
1347              # Verbose modes need a full traceback
1348              return VerboseTB.structured_traceback(
1349                  self, etype, value, tb, tb_offset, number_of_lines_of_context
1350              )
1351          elif mode == 'Minimal':
1352              return ListTB.get_exception_only(self, etype, value)
1353          else:
1354              # We must check the source cache because otherwise we can print
1355              # out-of-date source code.
1356              self.check_cache()
1357              # Now we can extract and format the exception
1358              return ListTB.structured_traceback(
1359                  self, etype, value, tb, tb_offset, number_of_lines_of_context
1360              )
1361  
1362      def stb2text(self, stb):
1363          """Convert a structured traceback (a list) to a string."""
1364          return self.tb_join_char.join(stb)
1365  
1366      def set_mode(self, mode: Optional[str] = None):
1367          """Switch to the desired mode.
1368  
1369          If mode is not specified, cycles through the available modes."""
1370  
1371          if not mode:
1372              new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1373                        len(self.valid_modes)
1374              self.mode = self.valid_modes[new_idx]
1375          elif mode not in self.valid_modes:
1376              raise ValueError(
1377                  "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1378                  "Valid modes: " + str(self.valid_modes)
1379              )
1380          else:
1381              assert isinstance(mode, str)
1382              self.mode = mode
1383          # include variable details only in 'Verbose' mode
1384          self.include_vars = (self.mode == self.valid_modes[2])
1385          # Set the join character for generating text tracebacks
1386          self.tb_join_char = self._join_chars[self.mode]
1387  
1388      # some convenient shortcuts
1389      def plain(self):
1390          self.set_mode(self.valid_modes[0])
1391  
1392      def context(self):
1393          self.set_mode(self.valid_modes[1])
1394  
1395      def verbose(self):
1396          self.set_mode(self.valid_modes[2])
1397  
1398      def minimal(self):
1399          self.set_mode(self.valid_modes[3])
1400  
1401  
1402  #----------------------------------------------------------------------------
1403  class AutoFormattedTB(FormattedTB):
1404      """A traceback printer which can be called on the fly.
1405  
1406      It will find out about exceptions by itself.
1407  
1408      A brief example::
1409  
1410          AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1411          try:
1412            ...
1413          except:
1414            AutoTB()  # or AutoTB(out=logfile) where logfile is an open file object
1415      """
1416  
1417      def __call__(self, etype=None, evalue=None, etb=None,
1418                   out=None, tb_offset=None):
1419          """Print out a formatted exception traceback.
1420  
1421          Optional arguments:
1422            - out: an open file-like object to direct output to.
1423  
1424            - tb_offset: the number of frames to skip over in the stack, on a
1425            per-call basis (this overrides temporarily the instance's tb_offset
1426            given at initialization time."""
1427  
1428          if out is None:
1429              out = self.ostream
1430          out.flush()
1431          out.write(self.text(etype, evalue, etb, tb_offset))
1432          out.write('\n')
1433          out.flush()
1434          # FIXME: we should remove the auto pdb behavior from here and leave
1435          # that to the clients.
1436          try:
1437              self.debugger()
1438          except KeyboardInterrupt:
1439              print("\nKeyboardInterrupt")
1440  
1441      def structured_traceback(
1442          self,
1443          etype: type,
1444          evalue: Optional[BaseException],
1445          etb: Optional[TracebackType] = None,
1446          tb_offset: Optional[int] = None,
1447          number_of_lines_of_context: int = 5,
1448      ):
1449          # tb: TracebackType or tupleof tb types ?
1450          if etype is None:
1451              etype, evalue, etb = sys.exc_info()
1452          if isinstance(etb, tuple):
1453              # tb is a tuple if this is a chained exception.
1454              self.tb = etb[0]
1455          else:
1456              self.tb = etb
1457          return FormattedTB.structured_traceback(
1458              self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1459          )
1460  
1461  
1462  #---------------------------------------------------------------------------
1463  
1464  # A simple class to preserve Nathan's original functionality.
1465  class ColorTB(FormattedTB):
1466      """Shorthand to initialize a FormattedTB in Linux colors mode."""
1467  
1468      def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1469          FormattedTB.__init__(self, color_scheme=color_scheme,
1470                               call_pdb=call_pdb, **kwargs)
1471  
1472  
1473  class SyntaxTB(ListTB):
1474      """Extension which holds some state: the last exception value"""
1475  
1476      def __init__(self, color_scheme='NoColor', parent=None, config=None):
1477          ListTB.__init__(self, color_scheme, parent=parent, config=config)
1478          self.last_syntax_error = None
1479  
1480      def __call__(self, etype, value, elist):
1481          self.last_syntax_error = value
1482  
1483          ListTB.__call__(self, etype, value, elist)
1484  
1485      def structured_traceback(self, etype, value, elist, tb_offset=None,
1486                               context=5):
1487          # If the source file has been edited, the line in the syntax error can
1488          # be wrong (retrieved from an outdated cache). This replaces it with
1489          # the current value.
1490          if isinstance(value, SyntaxError) \
1491                  and isinstance(value.filename, str) \
1492                  and isinstance(value.lineno, int):
1493              linecache.checkcache(value.filename)
1494              newtext = linecache.getline(value.filename, value.lineno)
1495              if newtext:
1496                  value.text = newtext
1497          self.last_syntax_error = value
1498          return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1499                                                            tb_offset=tb_offset, context=context)
1500  
1501      def clear_err_state(self):
1502          """Return the current error state and clear it"""
1503          e = self.last_syntax_error
1504          self.last_syntax_error = None
1505          return e
1506  
1507      def stb2text(self, stb):
1508          """Convert a structured traceback (a list) to a string."""
1509          return ''.join(stb)
1510  
1511  
1512  # some internal-use functions
1513  def text_repr(value):
1514      """Hopefully pretty robust repr equivalent."""
1515      # this is pretty horrible but should always return *something*
1516      try:
1517          return pydoc.text.repr(value)  # type: ignore[call-arg]
1518      except KeyboardInterrupt:
1519          raise
1520      except:
1521          try:
1522              return repr(value)
1523          except KeyboardInterrupt:
1524              raise
1525          except:
1526              try:
1527                  # all still in an except block so we catch
1528                  # getattr raising
1529                  name = getattr(value, '__name__', None)
1530                  if name:
1531                      # ick, recursion
1532                      return text_repr(name)
1533                  klass = getattr(value, '__class__', None)
1534                  if klass:
1535                      return '%s instance' % text_repr(klass)
1536              except KeyboardInterrupt:
1537                  raise
1538              except:
1539                  return 'UNRECOVERABLE REPR FAILURE'
1540  
1541  
1542  def eqrepr(value, repr=text_repr):
1543      return '=%s' % repr(value)
1544  
1545  
1546  def nullrepr(value, repr=text_repr):
1547      return ''
1548