Format: text/x-python
⬇ Download File (38.1 KB)
   1  """
   2  Pdb debugger class.
   3  
   4  
   5  This is an extension to PDB which adds a number of new features.
   6  Note that there is also the `IPython.terminal.debugger` class which provides UI
   7  improvements.
   8  
   9  We also strongly recommend to use this via the `ipdb` package, which provides
  10  extra configuration options.
  11  
  12  Among other things, this subclass of PDB:
  13   - supports many IPython magics like pdef/psource
  14   - hide frames in tracebacks based on `__tracebackhide__`
  15   - allows to skip frames based on `__debuggerskip__`
  16  
  17  
  18  Global Configuration
  19  --------------------
  20  
  21  The IPython debugger will by read the global ``~/.pdbrc`` file.
  22  That is to say you can list all commands supported by ipdb in your `~/.pdbrc`
  23  configuration file, to globally configure pdb.
  24  
  25  Example::
  26  
  27     # ~/.pdbrc
  28     skip_predicates debuggerskip false
  29     skip_hidden false
  30     context 25
  31  
  32  Features
  33  --------
  34  
  35  The IPython debugger can hide and skip frames when printing or moving through
  36  the stack. This can have a performance impact, so can be configures.
  37  
  38  The skipping and hiding frames are configurable via the `skip_predicates`
  39  command.
  40  
  41  By default, frames from readonly files will be hidden, frames containing
  42  ``__tracebackhide__ = True`` will be hidden.
  43  
  44  Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent
  45  frames value of ``__debuggerskip__`` is ``True`` will also be skipped.
  46  
  47      >>> def helpers_helper():
  48      ...     pass
  49      ...
  50      ... def helper_1():
  51      ...     print("don't step in me")
  52      ...     helpers_helpers() # will be stepped over unless breakpoint set.
  53      ...
  54      ...
  55      ... def helper_2():
  56      ...     print("in me neither")
  57      ...
  58  
  59  One can define a decorator that wraps a function between the two helpers:
  60  
  61      >>> def pdb_skipped_decorator(function):
  62      ...
  63      ...
  64      ...     def wrapped_fn(*args, **kwargs):
  65      ...         __debuggerskip__ = True
  66      ...         helper_1()
  67      ...         __debuggerskip__ = False
  68      ...         result = function(*args, **kwargs)
  69      ...         __debuggerskip__ = True
  70      ...         helper_2()
  71      ...         # setting __debuggerskip__ to False again is not necessary
  72      ...         return result
  73      ...
  74      ...     return wrapped_fn
  75  
  76  When decorating a function, ipdb will directly step into ``bar()`` by
  77  default:
  78  
  79      >>> @foo_decorator
  80      ... def bar(x, y):
  81      ...     return x * y
  82  
  83  
  84  You can toggle the behavior with
  85  
  86      ipdb> skip_predicates debuggerskip false
  87  
  88  or configure it in your ``.pdbrc``
  89  
  90  
  91  
  92  License
  93  -------
  94  
  95  Modified from the standard pdb.Pdb class to avoid including readline, so that
  96  the command line completion of other programs which include this isn't
  97  damaged.
  98  
  99  In the future, this class will be expanded with improvements over the standard
 100  pdb.
 101  
 102  The original code in this file is mainly lifted out of cmd.py in Python 2.2,
 103  with minor changes. Licensing should therefore be under the standard Python
 104  terms.  For details on the PSF (Python Software Foundation) standard license,
 105  see:
 106  
 107  https://docs.python.org/2/license.html
 108  
 109  
 110  All the changes since then are under the same license as IPython.
 111  
 112  """
 113  
 114  #*****************************************************************************
 115  #
 116  #       This file is licensed under the PSF license.
 117  #
 118  #       Copyright (C) 2001 Python Software Foundation, www.python.org
 119  #       Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
 120  #
 121  #
 122  #*****************************************************************************
 123  
 124  from __future__ import annotations
 125  
 126  import inspect
 127  import linecache
 128  import os
 129  import re
 130  import sys
 131  from contextlib import contextmanager
 132  from functools import lru_cache
 133  
 134  from IPython import get_ipython
 135  from IPython.core.excolors import exception_colors
 136  from IPython.utils import PyColorize, coloransi, py3compat
 137  
 138  from typing import TYPE_CHECKING
 139  
 140  if TYPE_CHECKING:
 141      # otherwise circular import
 142      from IPython.core.interactiveshell import InteractiveShell
 143  
 144  # skip module docstests
 145  __skip_doctest__ = True
 146  
 147  prompt = 'ipdb> '
 148  
 149  # We have to check this directly from sys.argv, config struct not yet available
 150  from pdb import Pdb as OldPdb
 151  
 152  # Allow the set_trace code to operate outside of an ipython instance, even if
 153  # it does so with some limitations.  The rest of this support is implemented in
 154  # the Tracer constructor.
 155  
 156  DEBUGGERSKIP = "__debuggerskip__"
 157  
 158  
 159  # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
 160  # on lower python versions, we backported the feature.
 161  CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
 162  
 163  
 164  def make_arrow(pad):
 165      """generate the leading arrow in front of traceback or debugger"""
 166      if pad >= 2:
 167          return '-'*(pad-2) + '> '
 168      elif pad == 1:
 169          return '>'
 170      return ''
 171  
 172  
 173  def BdbQuit_excepthook(et, ev, tb, excepthook=None):
 174      """Exception hook which handles `BdbQuit` exceptions.
 175  
 176      All other exceptions are processed using the `excepthook`
 177      parameter.
 178      """
 179      raise ValueError(
 180          "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.",
 181      )
 182  
 183  
 184  RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
 185  
 186  
 187  def strip_indentation(multiline_string):
 188      return RGX_EXTRA_INDENT.sub('', multiline_string)
 189  
 190  
 191  def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
 192      """Make new_fn have old_fn's doc string. This is particularly useful
 193      for the ``do_...`` commands that hook into the help system.
 194      Adapted from from a comp.lang.python posting
 195      by Duncan Booth."""
 196      def wrapper(*args, **kw):
 197          return new_fn(*args, **kw)
 198      if old_fn.__doc__:
 199          wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
 200      return wrapper
 201  
 202  
 203  class Pdb(OldPdb):
 204      """Modified Pdb class, does not load readline.
 205  
 206      for a standalone version that uses prompt_toolkit, see
 207      `IPython.terminal.debugger.TerminalPdb` and
 208      `IPython.terminal.debugger.set_trace()`
 209  
 210  
 211      This debugger can hide and skip frames that are tagged according to some predicates.
 212      See the `skip_predicates` commands.
 213  
 214      """
 215  
 216      shell: InteractiveShell
 217  
 218      if CHAIN_EXCEPTIONS:
 219          MAX_CHAINED_EXCEPTION_DEPTH = 999
 220  
 221      default_predicates = {
 222          "tbhide": True,
 223          "readonly": False,
 224          "ipython_internal": True,
 225          "debuggerskip": True,
 226      }
 227  
 228      def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
 229          """Create a new IPython debugger.
 230  
 231          Parameters
 232          ----------
 233          completekey : default None
 234              Passed to pdb.Pdb.
 235          stdin : default None
 236              Passed to pdb.Pdb.
 237          stdout : default None
 238              Passed to pdb.Pdb.
 239          context : int
 240              Number of lines of source code context to show when
 241              displaying stacktrace information.
 242          **kwargs
 243              Passed to pdb.Pdb.
 244  
 245          Notes
 246          -----
 247          The possibilities are python version dependent, see the python
 248          docs for more info.
 249          """
 250  
 251          # Parent constructor:
 252          try:
 253              self.context = int(context)
 254              if self.context <= 0:
 255                  raise ValueError("Context must be a positive integer")
 256          except (TypeError, ValueError) as e:
 257                  raise ValueError("Context must be a positive integer") from e
 258  
 259          # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
 260          OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
 261  
 262          # IPython changes...
 263          self.shell = get_ipython()
 264  
 265          if self.shell is None:
 266              save_main = sys.modules['__main__']
 267              # No IPython instance running, we must create one
 268              from IPython.terminal.interactiveshell import \
 269                  TerminalInteractiveShell
 270              self.shell = TerminalInteractiveShell.instance()
 271              # needed by any code which calls __import__("__main__") after
 272              # the debugger was entered. See also #9941.
 273              sys.modules["__main__"] = save_main
 274  
 275  
 276          color_scheme = self.shell.colors
 277  
 278          self.aliases = {}
 279  
 280          # Create color table: we copy the default one from the traceback
 281          # module and add a few attributes needed for debugging
 282          self.color_scheme_table = exception_colors()
 283  
 284          # shorthands
 285          C = coloransi.TermColors
 286          cst = self.color_scheme_table
 287  
 288  
 289          # Add a python parser so we can syntax highlight source while
 290          # debugging.
 291          self.parser = PyColorize.Parser(style=color_scheme)
 292          self.set_colors(color_scheme)
 293  
 294          # Set the prompt - the default prompt is '(Pdb)'
 295          self.prompt = prompt
 296          self.skip_hidden = True
 297          self.report_skipped = True
 298  
 299          # list of predicates we use to skip frames
 300          self._predicates = self.default_predicates
 301  
 302          if CHAIN_EXCEPTIONS:
 303              self._chained_exceptions = tuple()
 304              self._chained_exception_index = 0
 305  
 306      #
 307      def set_colors(self, scheme):
 308          """Shorthand access to the color table scheme selector method."""
 309          self.color_scheme_table.set_active_scheme(scheme)
 310          self.parser.style = scheme
 311  
 312      def set_trace(self, frame=None):
 313          if frame is None:
 314              frame = sys._getframe().f_back
 315          self.initial_frame = frame
 316          return super().set_trace(frame)
 317  
 318      def _hidden_predicate(self, frame):
 319          """
 320          Given a frame return whether it it should be hidden or not by IPython.
 321          """
 322  
 323          if self._predicates["readonly"]:
 324              fname = frame.f_code.co_filename
 325              # we need to check for file existence and interactively define
 326              # function would otherwise appear as RO.
 327              if os.path.isfile(fname) and not os.access(fname, os.W_OK):
 328                  return True
 329  
 330          if self._predicates["tbhide"]:
 331              if frame in (self.curframe, getattr(self, "initial_frame", None)):
 332                  return False
 333              frame_locals = self._get_frame_locals(frame)
 334              if "__tracebackhide__" not in frame_locals:
 335                  return False
 336              return frame_locals["__tracebackhide__"]
 337          return False
 338  
 339      def hidden_frames(self, stack):
 340          """
 341          Given an index in the stack return whether it should be skipped.
 342  
 343          This is used in up/down and where to skip frames.
 344          """
 345          # The f_locals dictionary is updated from the actual frame
 346          # locals whenever the .f_locals accessor is called, so we
 347          # avoid calling it here to preserve self.curframe_locals.
 348          # Furthermore, there is no good reason to hide the current frame.
 349          ip_hide = [self._hidden_predicate(s[0]) for s in stack]
 350          ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
 351          if ip_start and self._predicates["ipython_internal"]:
 352              ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
 353          return ip_hide
 354  
 355      if CHAIN_EXCEPTIONS:
 356  
 357          def _get_tb_and_exceptions(self, tb_or_exc):
 358              """
 359              Given a tracecack or an exception, return a tuple of chained exceptions
 360              and current traceback to inspect.
 361              This will deal with selecting the right ``__cause__`` or ``__context__``
 362              as well as handling cycles, and return a flattened list of exceptions we
 363              can jump to with do_exceptions.
 364              """
 365              _exceptions = []
 366              if isinstance(tb_or_exc, BaseException):
 367                  traceback, current = tb_or_exc.__traceback__, tb_or_exc
 368  
 369                  while current is not None:
 370                      if current in _exceptions:
 371                          break
 372                      _exceptions.append(current)
 373                      if current.__cause__ is not None:
 374                          current = current.__cause__
 375                      elif (
 376                          current.__context__ is not None
 377                          and not current.__suppress_context__
 378                      ):
 379                          current = current.__context__
 380  
 381                      if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
 382                          self.message(
 383                              f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
 384                              " chained exceptions found, not all exceptions"
 385                              "will be browsable with `exceptions`."
 386                          )
 387                          break
 388              else:
 389                  traceback = tb_or_exc
 390              return tuple(reversed(_exceptions)), traceback
 391  
 392          @contextmanager
 393          def _hold_exceptions(self, exceptions):
 394              """
 395              Context manager to ensure proper cleaning of exceptions references
 396              When given a chained exception instead of a traceback,
 397              pdb may hold references to many objects which may leak memory.
 398              We use this context manager to make sure everything is properly cleaned
 399              """
 400              try:
 401                  self._chained_exceptions = exceptions
 402                  self._chained_exception_index = len(exceptions) - 1
 403                  yield
 404              finally:
 405                  # we can't put those in forget as otherwise they would
 406                  # be cleared on exception change
 407                  self._chained_exceptions = tuple()
 408                  self._chained_exception_index = 0
 409  
 410          def do_exceptions(self, arg):
 411              """exceptions [number]
 412              List or change current exception in an exception chain.
 413              Without arguments, list all the current exception in the exception
 414              chain. Exceptions will be numbered, with the current exception indicated
 415              with an arrow.
 416              If given an integer as argument, switch to the exception at that index.
 417              """
 418              if not self._chained_exceptions:
 419                  self.message(
 420                      "Did not find chained exceptions. To move between"
 421                      " exceptions, pdb/post_mortem must be given an exception"
 422                      " object rather than a traceback."
 423                  )
 424                  return
 425              if not arg:
 426                  for ix, exc in enumerate(self._chained_exceptions):
 427                      prompt = ">" if ix == self._chained_exception_index else " "
 428                      rep = repr(exc)
 429                      if len(rep) > 80:
 430                          rep = rep[:77] + "..."
 431                      indicator = (
 432                          "  -"
 433                          if self._chained_exceptions[ix].__traceback__ is None
 434                          else f"{ix:>3}"
 435                      )
 436                      self.message(f"{prompt} {indicator} {rep}")
 437              else:
 438                  try:
 439                      number = int(arg)
 440                  except ValueError:
 441                      self.error("Argument must be an integer")
 442                      return
 443                  if 0 <= number < len(self._chained_exceptions):
 444                      if self._chained_exceptions[number].__traceback__ is None:
 445                          self.error(
 446                              "This exception does not have a traceback, cannot jump to it"
 447                          )
 448                          return
 449  
 450                      self._chained_exception_index = number
 451                      self.setup(None, self._chained_exceptions[number].__traceback__)
 452                      self.print_stack_entry(self.stack[self.curindex])
 453                  else:
 454                      self.error("No exception with that number")
 455  
 456      def interaction(self, frame, tb_or_exc):
 457          try:
 458              if CHAIN_EXCEPTIONS:
 459                  # this context manager is part of interaction in 3.13
 460                  _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
 461                  if isinstance(tb_or_exc, BaseException):
 462                      assert tb is not None, "main exception must have a traceback"
 463                  with self._hold_exceptions(_chained_exceptions):
 464                      OldPdb.interaction(self, frame, tb)
 465              else:
 466                  OldPdb.interaction(self, frame, tb_or_exc)
 467  
 468          except KeyboardInterrupt:
 469              self.stdout.write("\n" + self.shell.get_exception_only())
 470  
 471      def precmd(self, line):
 472          """Perform useful escapes on the command before it is executed."""
 473  
 474          if line.endswith("??"):
 475              line = "pinfo2 " + line[:-2]
 476          elif line.endswith("?"):
 477              line = "pinfo " + line[:-1]
 478  
 479          line = super().precmd(line)
 480  
 481          return line
 482  
 483      def new_do_quit(self, arg):
 484          return OldPdb.do_quit(self, arg)
 485  
 486      do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
 487  
 488      def print_stack_trace(self, context=None):
 489          Colors = self.color_scheme_table.active_colors
 490          ColorsNormal = Colors.Normal
 491          if context is None:
 492              context = self.context
 493          try:
 494              context = int(context)
 495              if context <= 0:
 496                  raise ValueError("Context must be a positive integer")
 497          except (TypeError, ValueError) as e:
 498                  raise ValueError("Context must be a positive integer") from e
 499          try:
 500              skipped = 0
 501              for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
 502                  if hidden and self.skip_hidden:
 503                      skipped += 1
 504                      continue
 505                  if skipped:
 506                      print(
 507                          f"{Colors.excName}    [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
 508                      )
 509                      skipped = 0
 510                  self.print_stack_entry(frame_lineno, context=context)
 511              if skipped:
 512                  print(
 513                      f"{Colors.excName}    [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
 514                  )
 515          except KeyboardInterrupt:
 516              pass
 517  
 518      def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
 519                            context=None):
 520          if context is None:
 521              context = self.context
 522          try:
 523              context = int(context)
 524              if context <= 0:
 525                  raise ValueError("Context must be a positive integer")
 526          except (TypeError, ValueError) as e:
 527                  raise ValueError("Context must be a positive integer") from e
 528          print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
 529  
 530          # vds: >>
 531          frame, lineno = frame_lineno
 532          filename = frame.f_code.co_filename
 533          self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
 534          # vds: <<
 535  
 536      def _get_frame_locals(self, frame):
 537          """ "
 538          Accessing f_local of current frame reset the namespace, so we want to avoid
 539          that or the following can happen
 540  
 541          ipdb> foo
 542          "old"
 543          ipdb> foo = "new"
 544          ipdb> foo
 545          "new"
 546          ipdb> where
 547          ipdb> foo
 548          "old"
 549  
 550          So if frame is self.current_frame we instead return self.curframe_locals
 551  
 552          """
 553          if frame is getattr(self, "curframe", None):
 554              return self.curframe_locals
 555          else:
 556              return frame.f_locals
 557  
 558      def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
 559          if context is None:
 560              context = self.context
 561          try:
 562              context = int(context)
 563              if context <= 0:
 564                  print("Context must be a positive integer", file=self.stdout)
 565          except (TypeError, ValueError):
 566                  print("Context must be a positive integer", file=self.stdout)
 567  
 568          import reprlib
 569  
 570          ret = []
 571  
 572          Colors = self.color_scheme_table.active_colors
 573          ColorsNormal = Colors.Normal
 574          tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
 575          tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
 576          tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
 577          tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
 578  
 579          frame, lineno = frame_lineno
 580  
 581          return_value = ''
 582          loc_frame = self._get_frame_locals(frame)
 583          if "__return__" in loc_frame:
 584              rv = loc_frame["__return__"]
 585              # return_value += '->'
 586              return_value += reprlib.repr(rv) + "\n"
 587          ret.append(return_value)
 588  
 589          #s = filename + '(' + `lineno` + ')'
 590          filename = self.canonic(frame.f_code.co_filename)
 591          link = tpl_link % py3compat.cast_unicode(filename)
 592  
 593          if frame.f_code.co_name:
 594              func = frame.f_code.co_name
 595          else:
 596              func = "<lambda>"
 597  
 598          call = ""
 599          if func != "?":
 600              if "__args__" in loc_frame:
 601                  args = reprlib.repr(loc_frame["__args__"])
 602              else:
 603                  args = '()'
 604              call = tpl_call % (func, args)
 605  
 606          # The level info should be generated in the same format pdb uses, to
 607          # avoid breaking the pdbtrack functionality of python-mode in *emacs.
 608          if frame is self.curframe:
 609              ret.append('> ')
 610          else:
 611              ret.append("  ")
 612          ret.append("%s(%s)%s\n" % (link, lineno, call))
 613  
 614          start = lineno - 1 - context//2
 615          lines = linecache.getlines(filename)
 616          start = min(start, len(lines) - context)
 617          start = max(start, 0)
 618          lines = lines[start : start + context]
 619  
 620          for i, line in enumerate(lines):
 621              show_arrow = start + 1 + i == lineno
 622              linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
 623              ret.append(
 624                  self.__format_line(
 625                      linetpl, filename, start + 1 + i, line, arrow=show_arrow
 626                  )
 627              )
 628          return "".join(ret)
 629  
 630      def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
 631          bp_mark = ""
 632          bp_mark_color = ""
 633  
 634          new_line, err = self.parser.format2(line, 'str')
 635          if not err:
 636              line = new_line
 637  
 638          bp = None
 639          if lineno in self.get_file_breaks(filename):
 640              bps = self.get_breaks(filename, lineno)
 641              bp = bps[-1]
 642  
 643          if bp:
 644              Colors = self.color_scheme_table.active_colors
 645              bp_mark = str(bp.number)
 646              bp_mark_color = Colors.breakpoint_enabled
 647              if not bp.enabled:
 648                  bp_mark_color = Colors.breakpoint_disabled
 649  
 650          numbers_width = 7
 651          if arrow:
 652              # This is the line with the error
 653              pad = numbers_width - len(str(lineno)) - len(bp_mark)
 654              num = '%s%s' % (make_arrow(pad), str(lineno))
 655          else:
 656              num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
 657  
 658          return tpl_line % (bp_mark_color + bp_mark, num, line)
 659  
 660      def print_list_lines(self, filename, first, last):
 661          """The printing (as opposed to the parsing part of a 'list'
 662          command."""
 663          try:
 664              Colors = self.color_scheme_table.active_colors
 665              ColorsNormal = Colors.Normal
 666              tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
 667              tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
 668              src = []
 669              if filename == "<string>" and hasattr(self, "_exec_filename"):
 670                  filename = self._exec_filename
 671  
 672              for lineno in range(first, last+1):
 673                  line = linecache.getline(filename, lineno)
 674                  if not line:
 675                      break
 676  
 677                  if lineno == self.curframe.f_lineno:
 678                      line = self.__format_line(
 679                          tpl_line_em, filename, lineno, line, arrow=True
 680                      )
 681                  else:
 682                      line = self.__format_line(
 683                          tpl_line, filename, lineno, line, arrow=False
 684                      )
 685  
 686                  src.append(line)
 687                  self.lineno = lineno
 688  
 689              print(''.join(src), file=self.stdout)
 690  
 691          except KeyboardInterrupt:
 692              pass
 693  
 694      def do_skip_predicates(self, args):
 695          """
 696          Turn on/off individual predicates as to whether a frame should be hidden/skip.
 697  
 698          The global option to skip (or not) hidden frames is set with skip_hidden
 699  
 700          To change the value of a predicate
 701  
 702              skip_predicates key [true|false]
 703  
 704          Call without arguments to see the current values.
 705  
 706          To permanently change the value of an option add the corresponding
 707          command to your ``~/.pdbrc`` file. If you are programmatically using the
 708          Pdb instance you can also change the ``default_predicates`` class
 709          attribute.
 710          """
 711          if not args.strip():
 712              print("current predicates:")
 713              for p, v in self._predicates.items():
 714                  print("   ", p, ":", v)
 715              return
 716          type_value = args.strip().split(" ")
 717          if len(type_value) != 2:
 718              print(
 719                  f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
 720              )
 721              return
 722  
 723          type_, value = type_value
 724          if type_ not in self._predicates:
 725              print(f"{type_!r} not in {set(self._predicates.keys())}")
 726              return
 727          if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
 728              print(
 729                  f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
 730              )
 731              return
 732  
 733          self._predicates[type_] = value.lower() in ("true", "yes", "1")
 734          if not any(self._predicates.values()):
 735              print(
 736                  "Warning, all predicates set to False, skip_hidden may not have any effects."
 737              )
 738  
 739      def do_skip_hidden(self, arg):
 740          """
 741          Change whether or not we should skip frames with the
 742          __tracebackhide__ attribute.
 743          """
 744          if not arg.strip():
 745              print(
 746                  f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
 747              )
 748          elif arg.strip().lower() in ("true", "yes"):
 749              self.skip_hidden = True
 750          elif arg.strip().lower() in ("false", "no"):
 751              self.skip_hidden = False
 752          if not any(self._predicates.values()):
 753              print(
 754                  "Warning, all predicates set to False, skip_hidden may not have any effects."
 755              )
 756  
 757      def do_list(self, arg):
 758          """Print lines of code from the current stack frame
 759          """
 760          self.lastcmd = 'list'
 761          last = None
 762          if arg and arg != ".":
 763              try:
 764                  x = eval(arg, {}, {})
 765                  if type(x) == type(()):
 766                      first, last = x
 767                      first = int(first)
 768                      last = int(last)
 769                      if last < first:
 770                          # Assume it's a count
 771                          last = first + last
 772                  else:
 773                      first = max(1, int(x) - 5)
 774              except:
 775                  print('*** Error in argument:', repr(arg), file=self.stdout)
 776                  return
 777          elif self.lineno is None or arg == ".":
 778              first = max(1, self.curframe.f_lineno - 5)
 779          else:
 780              first = self.lineno + 1
 781          if last is None:
 782              last = first + 10
 783          self.print_list_lines(self.curframe.f_code.co_filename, first, last)
 784  
 785          # vds: >>
 786          lineno = first
 787          filename = self.curframe.f_code.co_filename
 788          self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
 789          # vds: <<
 790  
 791      do_l = do_list
 792  
 793      def getsourcelines(self, obj):
 794          lines, lineno = inspect.findsource(obj)
 795          if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
 796              # must be a module frame: do not try to cut a block out of it
 797              return lines, 1
 798          elif inspect.ismodule(obj):
 799              return lines, 1
 800          return inspect.getblock(lines[lineno:]), lineno+1
 801  
 802      def do_longlist(self, arg):
 803          """Print lines of code from the current stack frame.
 804  
 805          Shows more lines than 'list' does.
 806          """
 807          self.lastcmd = 'longlist'
 808          try:
 809              lines, lineno = self.getsourcelines(self.curframe)
 810          except OSError as err:
 811              self.error(err)
 812              return
 813          last = lineno + len(lines)
 814          self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
 815      do_ll = do_longlist
 816  
 817      def do_debug(self, arg):
 818          """debug code
 819          Enter a recursive debugger that steps through the code
 820          argument (which is an arbitrary expression or statement to be
 821          executed in the current environment).
 822          """
 823          trace_function = sys.gettrace()
 824          sys.settrace(None)
 825          globals = self.curframe.f_globals
 826          locals = self.curframe_locals
 827          p = self.__class__(completekey=self.completekey,
 828                             stdin=self.stdin, stdout=self.stdout)
 829          p.use_rawinput = self.use_rawinput
 830          p.prompt = "(%s) " % self.prompt.strip()
 831          self.message("ENTERING RECURSIVE DEBUGGER")
 832          sys.call_tracing(p.run, (arg, globals, locals))
 833          self.message("LEAVING RECURSIVE DEBUGGER")
 834          sys.settrace(trace_function)
 835          self.lastcmd = p.lastcmd
 836  
 837      def do_pdef(self, arg):
 838          """Print the call signature for any callable object.
 839  
 840          The debugger interface to %pdef"""
 841          namespaces = [
 842              ("Locals", self.curframe_locals),
 843              ("Globals", self.curframe.f_globals),
 844          ]
 845          self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
 846  
 847      def do_pdoc(self, arg):
 848          """Print the docstring for an object.
 849  
 850          The debugger interface to %pdoc."""
 851          namespaces = [
 852              ("Locals", self.curframe_locals),
 853              ("Globals", self.curframe.f_globals),
 854          ]
 855          self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
 856  
 857      def do_pfile(self, arg):
 858          """Print (or run through pager) the file where an object is defined.
 859  
 860          The debugger interface to %pfile.
 861          """
 862          namespaces = [
 863              ("Locals", self.curframe_locals),
 864              ("Globals", self.curframe.f_globals),
 865          ]
 866          self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
 867  
 868      def do_pinfo(self, arg):
 869          """Provide detailed information about an object.
 870  
 871          The debugger interface to %pinfo, i.e., obj?."""
 872          namespaces = [
 873              ("Locals", self.curframe_locals),
 874              ("Globals", self.curframe.f_globals),
 875          ]
 876          self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
 877  
 878      def do_pinfo2(self, arg):
 879          """Provide extra detailed information about an object.
 880  
 881          The debugger interface to %pinfo2, i.e., obj??."""
 882          namespaces = [
 883              ("Locals", self.curframe_locals),
 884              ("Globals", self.curframe.f_globals),
 885          ]
 886          self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
 887  
 888      def do_psource(self, arg):
 889          """Print (or run through pager) the source code for an object."""
 890          namespaces = [
 891              ("Locals", self.curframe_locals),
 892              ("Globals", self.curframe.f_globals),
 893          ]
 894          self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
 895  
 896      def do_where(self, arg):
 897          """w(here)
 898          Print a stack trace, with the most recent frame at the bottom.
 899          An arrow indicates the "current frame", which determines the
 900          context of most commands. 'bt' is an alias for this command.
 901  
 902          Take a number as argument as an (optional) number of context line to
 903          print"""
 904          if arg:
 905              try:
 906                  context = int(arg)
 907              except ValueError as err:
 908                  self.error(err)
 909                  return
 910              self.print_stack_trace(context)
 911          else:
 912              self.print_stack_trace()
 913  
 914      do_w = do_where
 915  
 916      def break_anywhere(self, frame):
 917          """
 918          _stop_in_decorator_internals is overly restrictive, as we may still want
 919          to trace function calls, so we need to also update break_anywhere so
 920          that is we don't `stop_here`, because of debugger skip, we may still
 921          stop at any point inside the function
 922  
 923          """
 924  
 925          sup = super().break_anywhere(frame)
 926          if sup:
 927              return sup
 928          if self._predicates["debuggerskip"]:
 929              if DEBUGGERSKIP in frame.f_code.co_varnames:
 930                  return True
 931              if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
 932                  return True
 933          return False
 934  
 935      def _is_in_decorator_internal_and_should_skip(self, frame):
 936          """
 937          Utility to tell us whether we are in a decorator internal and should stop.
 938  
 939          """
 940          # if we are disabled don't skip
 941          if not self._predicates["debuggerskip"]:
 942              return False
 943  
 944          return self._cachable_skip(frame)
 945  
 946      @lru_cache(1024)
 947      def _cached_one_parent_frame_debuggerskip(self, frame):
 948          """
 949          Cache looking up for DEBUGGERSKIP on parent frame.
 950  
 951          This should speedup walking through deep frame when one of the highest
 952          one does have a debugger skip.
 953  
 954          This is likely to introduce fake positive though.
 955          """
 956          while getattr(frame, "f_back", None):
 957              frame = frame.f_back
 958              if self._get_frame_locals(frame).get(DEBUGGERSKIP):
 959                  return True
 960          return None
 961  
 962      @lru_cache(1024)
 963      def _cachable_skip(self, frame):
 964          # if frame is tagged, skip by default.
 965          if DEBUGGERSKIP in frame.f_code.co_varnames:
 966              return True
 967  
 968          # if one of the parent frame value set to True skip as well.
 969          if self._cached_one_parent_frame_debuggerskip(frame):
 970              return True
 971  
 972          return False
 973  
 974      def stop_here(self, frame):
 975          if self._is_in_decorator_internal_and_should_skip(frame) is True:
 976              return False
 977  
 978          hidden = False
 979          if self.skip_hidden:
 980              hidden = self._hidden_predicate(frame)
 981          if hidden:
 982              if self.report_skipped:
 983                  Colors = self.color_scheme_table.active_colors
 984                  ColorsNormal = Colors.Normal
 985                  print(
 986                      f"{Colors.excName}    [... skipped 1 hidden frame]{ColorsNormal}\n"
 987                  )
 988          return super().stop_here(frame)
 989  
 990      def do_up(self, arg):
 991          """u(p) [count]
 992          Move the current frame count (default one) levels up in the
 993          stack trace (to an older frame).
 994  
 995          Will skip hidden frames.
 996          """
 997          # modified version of upstream that skips
 998          # frames with __tracebackhide__
 999          if self.curindex == 0:
1000              self.error("Oldest frame")
1001              return
1002          try:
1003              count = int(arg or 1)
1004          except ValueError:
1005              self.error("Invalid frame count (%s)" % arg)
1006              return
1007          skipped = 0
1008          if count < 0:
1009              _newframe = 0
1010          else:
1011              counter = 0
1012              hidden_frames = self.hidden_frames(self.stack)
1013              for i in range(self.curindex - 1, -1, -1):
1014                  if hidden_frames[i] and self.skip_hidden:
1015                      skipped += 1
1016                      continue
1017                  counter += 1
1018                  if counter >= count:
1019                      break
1020              else:
1021                  # if no break occurred.
1022                  self.error(
1023                      "all frames above hidden, use `skip_hidden False` to get get into those."
1024                  )
1025                  return
1026  
1027              Colors = self.color_scheme_table.active_colors
1028              ColorsNormal = Colors.Normal
1029              _newframe = i
1030          self._select_frame(_newframe)
1031          if skipped:
1032              print(
1033                  f"{Colors.excName}    [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1034              )
1035  
1036      def do_down(self, arg):
1037          """d(own) [count]
1038          Move the current frame count (default one) levels down in the
1039          stack trace (to a newer frame).
1040  
1041          Will skip hidden frames.
1042          """
1043          if self.curindex + 1 == len(self.stack):
1044              self.error("Newest frame")
1045              return
1046          try:
1047              count = int(arg or 1)
1048          except ValueError:
1049              self.error("Invalid frame count (%s)" % arg)
1050              return
1051          if count < 0:
1052              _newframe = len(self.stack) - 1
1053          else:
1054              counter = 0
1055              skipped = 0
1056              hidden_frames = self.hidden_frames(self.stack)
1057              for i in range(self.curindex + 1, len(self.stack)):
1058                  if hidden_frames[i] and self.skip_hidden:
1059                      skipped += 1
1060                      continue
1061                  counter += 1
1062                  if counter >= count:
1063                      break
1064              else:
1065                  self.error(
1066                      "all frames below hidden, use `skip_hidden False` to get get into those."
1067                  )
1068                  return
1069  
1070              Colors = self.color_scheme_table.active_colors
1071              ColorsNormal = Colors.Normal
1072              if skipped:
1073                  print(
1074                      f"{Colors.excName}    [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1075                  )
1076              _newframe = i
1077  
1078          self._select_frame(_newframe)
1079  
1080      do_d = do_down
1081      do_u = do_up
1082  
1083      def do_context(self, context):
1084          """context number_of_lines
1085          Set the number of lines of source code to show when displaying
1086          stacktrace information.
1087          """
1088          try:
1089              new_context = int(context)
1090              if new_context <= 0:
1091                  raise ValueError()
1092              self.context = new_context
1093          except ValueError:
1094              self.error(
1095                  f"The 'context' command requires a positive integer argument (current value {self.context})."
1096              )
1097  
1098  
1099  class InterruptiblePdb(Pdb):
1100      """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1101  
1102      def cmdloop(self, intro=None):
1103          """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1104          try:
1105              return OldPdb.cmdloop(self, intro=intro)
1106          except KeyboardInterrupt:
1107              self.stop_here = lambda frame: False
1108              self.do_quit("")
1109              sys.settrace(None)
1110              self.quitting = False
1111              raise
1112  
1113      def _cmdloop(self):
1114          while True:
1115              try:
1116                  # keyboard interrupts allow for an easy way to cancel
1117                  # the current command, so allow them during interactive input
1118                  self.allow_kbdint = True
1119                  self.cmdloop()
1120                  self.allow_kbdint = False
1121                  break
1122              except KeyboardInterrupt:
1123                  self.message('--KeyboardInterrupt--')
1124                  raise
1125  
1126  
1127  def set_trace(frame=None, header=None):
1128      """
1129      Start debugging from `frame`.
1130  
1131      If frame is not specified, debugging starts from caller's frame.
1132      """
1133      pdb = Pdb()
1134      if header is not None:
1135          pdb.message(header)
1136      pdb.set_trace(frame or sys._getframe().f_back)
1137