| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376 |
- """
- Pdb debugger class.
- This is an extension to PDB which adds a number of new features.
- Note that there is also the `IPython.terminal.debugger` class which provides UI
- improvements.
- We also strongly recommend to use this via the `ipdb` package, which provides
- extra configuration options.
- Among other things, this subclass of PDB:
- - supports many IPython magics like pdef/psource
- - hide frames in tracebacks based on `__tracebackhide__`
- - allows to skip frames based on `__debuggerskip__`
- Global Configuration
- --------------------
- The IPython debugger will by read the global ``~/.pdbrc`` file.
- That is to say you can list all commands supported by ipdb in your `~/.pdbrc`
- configuration file, to globally configure pdb.
- Example::
- # ~/.pdbrc
- skip_predicates debuggerskip false
- skip_hidden false
- context 25
- Features
- --------
- The IPython debugger can hide and skip frames when printing or moving through
- the stack. This can have a performance impact, so can be configures.
- The skipping and hiding frames are configurable via the `skip_predicates`
- command.
- By default, frames from readonly files will be hidden, frames containing
- ``__tracebackhide__ = True`` will be hidden.
- Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent
- frames value of ``__debuggerskip__`` is ``True`` will also be skipped.
- >>> def helpers_helper():
- ... pass
- ...
- ... def helper_1():
- ... print("don't step in me")
- ... helpers_helpers() # will be stepped over unless breakpoint set.
- ...
- ...
- ... def helper_2():
- ... print("in me neither")
- ...
- One can define a decorator that wraps a function between the two helpers:
- >>> def pdb_skipped_decorator(function):
- ...
- ...
- ... def wrapped_fn(*args, **kwargs):
- ... __debuggerskip__ = True
- ... helper_1()
- ... __debuggerskip__ = False
- ... result = function(*args, **kwargs)
- ... __debuggerskip__ = True
- ... helper_2()
- ... # setting __debuggerskip__ to False again is not necessary
- ... return result
- ...
- ... return wrapped_fn
- When decorating a function, ipdb will directly step into ``bar()`` by
- default:
- >>> @foo_decorator
- ... def bar(x, y):
- ... return x * y
- You can toggle the behavior with
- ipdb> skip_predicates debuggerskip false
- or configure it in your ``.pdbrc``
- License
- -------
- Modified from the standard pdb.Pdb class to avoid including readline, so that
- the command line completion of other programs which include this isn't
- damaged.
- In the future, this class will be expanded with improvements over the standard
- pdb.
- The original code in this file is mainly lifted out of cmd.py in Python 2.2,
- with minor changes. Licensing should therefore be under the standard Python
- terms. For details on the PSF (Python Software Foundation) standard license,
- see:
- https://docs.python.org/2/license.html
- All the changes since then are under the same license as IPython.
- """
- # *****************************************************************************
- #
- # This file is licensed under the PSF license.
- #
- # Copyright (C) 2001 Python Software Foundation, www.python.org
- # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
- #
- #
- # *****************************************************************************
- from __future__ import annotations
- import inspect
- import linecache
- import os
- import re
- import sys
- import warnings
- from contextlib import contextmanager
- from functools import lru_cache
- from IPython import get_ipython
- from IPython.core.debugger_backport import PdbClosureBackport
- from IPython.utils import PyColorize
- from IPython.utils.PyColorize import TokenStream
- from typing import TYPE_CHECKING
- from types import FrameType
- # We have to check this directly from sys.argv, config struct not yet available
- from pdb import Pdb as _OldPdb
- from pygments.token import Token
- if sys.version_info < (3, 13):
- class OldPdb(PdbClosureBackport, _OldPdb):
- pass
- else:
- OldPdb = _OldPdb
- if TYPE_CHECKING:
- # otherwise circular import
- from IPython.core.interactiveshell import InteractiveShell
- # skip module docstests
- __skip_doctest__ = True
- prompt = "ipdb> "
- # Allow the set_trace code to operate outside of an ipython instance, even if
- # it does so with some limitations. The rest of this support is implemented in
- # the Tracer constructor.
- DEBUGGERSKIP = "__debuggerskip__"
- # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
- # on lower python versions, we backported the feature.
- CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
- def BdbQuit_excepthook(et, ev, tb, excepthook=None):
- """Exception hook which handles `BdbQuit` exceptions.
- All other exceptions are processed using the `excepthook`
- parameter.
- """
- raise ValueError(
- "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.",
- )
- RGX_EXTRA_INDENT = re.compile(r"(?<=\n)\s+")
- def strip_indentation(multiline_string):
- return RGX_EXTRA_INDENT.sub("", multiline_string)
- def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
- """Make new_fn have old_fn's doc string. This is particularly useful
- for the ``do_...`` commands that hook into the help system.
- Adapted from from a comp.lang.python posting
- by Duncan Booth."""
- def wrapper(*args, **kw):
- return new_fn(*args, **kw)
- if old_fn.__doc__:
- wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
- return wrapper
- class Pdb(OldPdb):
- """Modified Pdb class, does not load readline.
- for a standalone version that uses prompt_toolkit, see
- `IPython.terminal.debugger.TerminalPdb` and
- `IPython.terminal.debugger.set_trace()`
- This debugger can hide and skip frames that are tagged according to some predicates.
- See the `skip_predicates` commands.
- """
- shell: InteractiveShell
- _theme_name: str
- _context: int
- _chained_exceptions: tuple[Exception, ...]
- _chained_exception_index: int
- if CHAIN_EXCEPTIONS:
- MAX_CHAINED_EXCEPTION_DEPTH = 999
- default_predicates = {
- "tbhide": True,
- "readonly": False,
- "ipython_internal": True,
- "debuggerskip": True,
- }
- def __init__(
- self,
- completekey=None,
- stdin=None,
- stdout=None,
- context: int | None | str = 5,
- **kwargs,
- ):
- """Create a new IPython debugger.
- Parameters
- ----------
- completekey : default None
- Passed to pdb.Pdb.
- stdin : default None
- Passed to pdb.Pdb.
- stdout : default None
- Passed to pdb.Pdb.
- context : int
- Number of lines of source code context to show when
- displaying stacktrace information.
- **kwargs
- Passed to pdb.Pdb.
- Notes
- -----
- The possibilities are python version dependent, see the python
- docs for more info.
- """
- # ipdb issue, see https://github.com/ipython/ipython/issues/14811
- if context is None:
- context = 5
- if isinstance(context, str):
- context = int(context)
- self.context = context
- # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
- OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
- # Python 3.15+ should define this, so no need to initialize
- # this avoids some getattr(self, 'curframe')
- if sys.version_info < (3, 15):
- self.curframe = None
- # IPython changes...
- self.shell = get_ipython()
- if self.shell is None:
- save_main = sys.modules["__main__"]
- # No IPython instance running, we must create one
- from IPython.terminal.interactiveshell import TerminalInteractiveShell
- self.shell = TerminalInteractiveShell.instance()
- # needed by any code which calls __import__("__main__") after
- # the debugger was entered. See also #9941.
- sys.modules["__main__"] = save_main
- self.aliases = {}
- theme_name = self.shell.colors
- assert isinstance(theme_name, str)
- assert theme_name.lower() == theme_name
- # Add a python parser so we can syntax highlight source while
- # debugging.
- self.parser = PyColorize.Parser(theme_name=theme_name)
- self.set_theme_name(theme_name)
- # Set the prompt - the default prompt is '(Pdb)'
- self.prompt = prompt
- self.skip_hidden = True
- self.report_skipped = True
- # list of predicates we use to skip frames
- self._predicates = self.default_predicates
- if CHAIN_EXCEPTIONS:
- self._chained_exceptions = tuple()
- self._chained_exception_index = 0
- @property
- def context(self) -> int:
- return self._context
- @context.setter
- def context(self, value: int | str) -> None:
- # ipdb issue see https://github.com/ipython/ipython/issues/14811
- if not isinstance(value, int):
- value = int(value)
- assert isinstance(value, int)
- assert value >= 0
- self._context = value
- def set_theme_name(self, name):
- assert name.lower() == name
- assert isinstance(name, str)
- self._theme_name = name
- self.parser.theme_name = name
- @property
- def theme(self):
- return PyColorize.theme_table[self._theme_name]
- #
- def set_colors(self, scheme):
- """Shorthand access to the color table scheme selector method."""
- warnings.warn(
- "set_colors is deprecated since IPython 9.0, use set_theme_name instead",
- DeprecationWarning,
- stacklevel=2,
- )
- assert scheme == scheme.lower()
- self._theme_name = scheme.lower()
- self.parser.theme_name = scheme.lower()
- def set_trace(self, frame=None):
- if frame is None:
- frame = sys._getframe().f_back
- self.initial_frame = frame
- return super().set_trace(frame)
- def get_stack(self, *args, **kwargs):
- stack, pos = super().get_stack(*args, **kwargs)
- if len(stack) >= 0 and self._is_internal_frame(stack[0][0]):
- stack.pop(0)
- pos -= 1
- return stack, pos
- def _is_internal_frame(self, frame):
- """Determine if this frame should be skipped as internal"""
- filename = frame.f_code.co_filename
- # Skip bdb.py runcall and internal operations
- if filename.endswith("bdb.py"):
- func_name = frame.f_code.co_name
- # Skip internal bdb operations but allow breakpoint hits
- if func_name in ("runcall", "run", "runeval"):
- return True
- return False
- def _hidden_predicate(self, frame):
- """
- Given a frame return whether it it should be hidden or not by IPython.
- """
- if self._predicates["readonly"]:
- fname = frame.f_code.co_filename
- # we need to check for file existence and interactively define
- # function would otherwise appear as RO.
- if os.path.isfile(fname) and not os.access(fname, os.W_OK):
- return True
- if self._predicates["tbhide"]:
- if frame in (self.curframe, getattr(self, "initial_frame", None)):
- return False
- frame_locals = self._get_frame_locals(frame)
- if "__tracebackhide__" not in frame_locals:
- return False
- return frame_locals["__tracebackhide__"]
- return False
- def hidden_frames(self, stack):
- """
- Given an index in the stack return whether it should be skipped.
- This is used in up/down and where to skip frames.
- """
- # The f_locals dictionary is updated from the actual frame
- # locals whenever the .f_locals accessor is called, so we
- # avoid calling it here to preserve self.curframe_locals.
- # Furthermore, there is no good reason to hide the current frame.
- ip_hide = [self._hidden_predicate(s[0]) for s in stack]
- ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
- if ip_start and self._predicates["ipython_internal"]:
- ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
- return ip_hide
- if CHAIN_EXCEPTIONS:
- def _get_tb_and_exceptions(self, tb_or_exc):
- """
- Given a tracecack or an exception, return a tuple of chained exceptions
- and current traceback to inspect.
- This will deal with selecting the right ``__cause__`` or ``__context__``
- as well as handling cycles, and return a flattened list of exceptions we
- can jump to with do_exceptions.
- """
- _exceptions = []
- if isinstance(tb_or_exc, BaseException):
- traceback, current = tb_or_exc.__traceback__, tb_or_exc
- while current is not None:
- if current in _exceptions:
- break
- _exceptions.append(current)
- if current.__cause__ is not None:
- current = current.__cause__
- elif (
- current.__context__ is not None
- and not current.__suppress_context__
- ):
- current = current.__context__
- if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
- self.message(
- f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
- " chained exceptions found, not all exceptions"
- " will be browsable with `exceptions`."
- )
- break
- else:
- traceback = tb_or_exc
- return tuple(reversed(_exceptions)), traceback
- @contextmanager
- def _hold_exceptions(self, exceptions):
- """
- Context manager to ensure proper cleaning of exceptions references
- When given a chained exception instead of a traceback,
- pdb may hold references to many objects which may leak memory.
- We use this context manager to make sure everything is properly cleaned
- """
- try:
- self._chained_exceptions = exceptions
- self._chained_exception_index = len(exceptions) - 1
- yield
- finally:
- # we can't put those in forget as otherwise they would
- # be cleared on exception change
- self._chained_exceptions = tuple()
- self._chained_exception_index = 0
- def do_exceptions(self, arg):
- """exceptions [number]
- List or change current exception in an exception chain.
- Without arguments, list all the current exception in the exception
- chain. Exceptions will be numbered, with the current exception indicated
- with an arrow.
- If given an integer as argument, switch to the exception at that index.
- """
- if not self._chained_exceptions:
- self.message(
- "Did not find chained exceptions. To move between"
- " exceptions, pdb/post_mortem must be given an exception"
- " object rather than a traceback."
- )
- return
- if not arg:
- for ix, exc in enumerate(self._chained_exceptions):
- prompt = ">" if ix == self._chained_exception_index else " "
- rep = repr(exc)
- if len(rep) > 80:
- rep = rep[:77] + "..."
- indicator = (
- " -"
- if self._chained_exceptions[ix].__traceback__ is None
- else f"{ix:>3}"
- )
- self.message(f"{prompt} {indicator} {rep}")
- else:
- try:
- number = int(arg)
- except ValueError:
- self.error("Argument must be an integer")
- return
- if 0 <= number < len(self._chained_exceptions):
- if self._chained_exceptions[number].__traceback__ is None:
- self.error(
- "This exception does not have a traceback, cannot jump to it"
- )
- return
- self._chained_exception_index = number
- self.setup(None, self._chained_exceptions[number].__traceback__)
- self.print_stack_entry(self.stack[self.curindex])
- else:
- self.error("No exception with that number")
- def interaction(self, frame, tb_or_exc):
- try:
- if CHAIN_EXCEPTIONS:
- # this context manager is part of interaction in 3.13
- _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
- if isinstance(tb_or_exc, BaseException):
- assert tb is not None, "main exception must have a traceback"
- with self._hold_exceptions(_chained_exceptions):
- OldPdb.interaction(self, frame, tb)
- else:
- OldPdb.interaction(self, frame, tb_or_exc)
- except KeyboardInterrupt:
- self.stdout.write("\n" + self.shell.get_exception_only())
- def precmd(self, line):
- """Perform useful escapes on the command before it is executed."""
- if line.endswith("??"):
- line = "pinfo2 " + line[:-2]
- elif line.endswith("?"):
- line = "pinfo " + line[:-1]
- line = super().precmd(line)
- return line
- def new_do_quit(self, arg):
- return OldPdb.do_quit(self, arg)
- do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
- def print_stack_trace(self, context: int | None = None):
- if context is None:
- context = self.context
- try:
- skipped = 0
- to_print = ""
- for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
- if hidden and self.skip_hidden:
- skipped += 1
- continue
- if skipped:
- to_print += self.theme.format(
- [
- (
- Token.ExcName,
- f" [... skipping {skipped} hidden frame(s)]",
- ),
- (Token, "\n"),
- ]
- )
- skipped = 0
- to_print += self.format_stack_entry(frame_lineno)
- if skipped:
- to_print += self.theme.format(
- [
- (
- Token.ExcName,
- f" [... skipping {skipped} hidden frame(s)]",
- ),
- (Token, "\n"),
- ]
- )
- print(to_print, file=self.stdout)
- except KeyboardInterrupt:
- pass
- def print_stack_entry(
- self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> "
- ) -> None:
- """
- Overwrite print_stack_entry from superclass (PDB)
- """
- print(self.format_stack_entry(frame_lineno, ""), file=self.stdout)
- frame, lineno = frame_lineno
- filename = frame.f_code.co_filename
- self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
- def _get_frame_locals(self, frame):
- """ "
- Accessing f_local of current frame reset the namespace, so we want to avoid
- that or the following can happen
- ipdb> foo
- "old"
- ipdb> foo = "new"
- ipdb> foo
- "new"
- ipdb> where
- ipdb> foo
- "old"
- So if frame is self.current_frame we instead return self.curframe_locals
- """
- if frame is self.curframe:
- return self.curframe_locals
- else:
- return frame.f_locals
- def format_stack_entry(
- self,
- frame_lineno: tuple[FrameType, int], # type: ignore[override] # stubs are wrong
- lprefix: str = ": ",
- ) -> str:
- """
- overwrite from super class so must -> str
- """
- context = self.context
- try:
- context = int(context)
- if context <= 0:
- print("Context must be a positive integer", file=self.stdout)
- except (TypeError, ValueError):
- print("Context must be a positive integer", file=self.stdout)
- import reprlib
- ret_tok = []
- frame, lineno = frame_lineno
- return_value = ""
- loc_frame = self._get_frame_locals(frame)
- if "__return__" in loc_frame:
- rv = loc_frame["__return__"]
- # return_value += '->'
- return_value += reprlib.repr(rv) + "\n"
- ret_tok.extend([(Token, return_value)])
- # s = filename + '(' + `lineno` + ')'
- filename = self.canonic(frame.f_code.co_filename)
- link_tok = (Token.FilenameEm, filename)
- if frame.f_code.co_name:
- func = frame.f_code.co_name
- else:
- func = "<lambda>"
- call_toks = []
- if func != "?":
- if "__args__" in loc_frame:
- args = reprlib.repr(loc_frame["__args__"])
- else:
- args = "()"
- call_toks = [(Token.VName, func), (Token.ValEm, args)]
- # The level info should be generated in the same format pdb uses, to
- # avoid breaking the pdbtrack functionality of python-mode in *emacs.
- if frame is self.curframe:
- ret_tok.append((Token.CurrentFrame, self.theme.make_arrow(2)))
- else:
- ret_tok.append((Token, " "))
- ret_tok.extend(
- [
- link_tok,
- (Token, "("),
- (Token.Lineno, str(lineno)),
- (Token, ")"),
- *call_toks,
- (Token, "\n"),
- ]
- )
- start = lineno - 1 - context // 2
- lines = linecache.getlines(filename)
- start = min(start, len(lines) - context)
- start = max(start, 0)
- lines = lines[start : start + context]
- for i, line in enumerate(lines):
- show_arrow = start + 1 + i == lineno
- bp, num, colored_line = self.__line_content(
- filename,
- start + 1 + i,
- line,
- arrow=show_arrow,
- )
- if frame is self.curframe or show_arrow:
- rlt = [
- bp,
- (Token.LinenoEm, num),
- (Token, " "),
- # TODO: investigate Toke.Line here, likely LineEm,
- # Token is problematic here as line is already colored, a
- # and this changes the full style of the colored line.
- # ideally, __line_content returns the token and we modify the style.
- (Token, colored_line),
- ]
- else:
- rlt = [
- bp,
- (Token.Lineno, num),
- (Token, " "),
- # TODO: investigate Toke.Line here, likely Line
- # Token is problematic here as line is already colored, a
- # and this changes the full style of the colored line.
- # ideally, __line_content returns the token and we modify the style.
- (Token.Line, colored_line),
- ]
- ret_tok.extend(rlt)
- return self.theme.format(ret_tok)
- def __line_content(
- self, filename: str, lineno: int, line: str, arrow: bool = False
- ):
- bp_mark = ""
- BreakpointToken = Token.Breakpoint
- new_line, err = self.parser.format2(line, "str")
- if not err:
- assert new_line is not None
- line = new_line
- bp = None
- if lineno in self.get_file_breaks(filename):
- bps = self.get_breaks(filename, lineno)
- bp = bps[-1]
- if bp:
- bp_mark = str(bp.number)
- BreakpointToken = Token.Breakpoint.Enabled
- if not bp.enabled:
- BreakpointToken = Token.Breakpoint.Disabled
- numbers_width = 7
- if arrow:
- # This is the line with the error
- pad = numbers_width - len(str(lineno)) - len(bp_mark)
- num = "%s%s" % (self.theme.make_arrow(pad), str(lineno))
- else:
- num = "%*s" % (numbers_width - len(bp_mark), str(lineno))
- bp_str = (BreakpointToken, bp_mark)
- return (bp_str, num, line)
- def print_list_lines(self, filename: str, first: int, last: int) -> None:
- """The printing (as opposed to the parsing part of a 'list'
- command."""
- toks: TokenStream = []
- try:
- if filename == "<string>" and hasattr(self, "_exec_filename"):
- filename = self._exec_filename
- for lineno in range(first, last + 1):
- line = linecache.getline(filename, lineno)
- if not line:
- break
- assert self.curframe is not None
- if lineno == self.curframe.f_lineno:
- bp, num, colored_line = self.__line_content(
- filename, lineno, line, arrow=True
- )
- toks.extend(
- [
- bp,
- (Token.LinenoEm, num),
- (Token, " "),
- # TODO: investigate Token.Line here
- (Token, colored_line),
- ]
- )
- else:
- bp, num, colored_line = self.__line_content(
- filename, lineno, line, arrow=False
- )
- toks.extend(
- [
- bp,
- (Token.Lineno, num),
- (Token, " "),
- (Token, colored_line),
- ]
- )
- self.lineno = lineno
- print(self.theme.format(toks), file=self.stdout)
- except KeyboardInterrupt:
- pass
- def do_skip_predicates(self, args):
- """
- Turn on/off individual predicates as to whether a frame should be hidden/skip.
- The global option to skip (or not) hidden frames is set with skip_hidden
- To change the value of a predicate
- skip_predicates key [true|false]
- Call without arguments to see the current values.
- To permanently change the value of an option add the corresponding
- command to your ``~/.pdbrc`` file. If you are programmatically using the
- Pdb instance you can also change the ``default_predicates`` class
- attribute.
- """
- if not args.strip():
- print("current predicates:")
- for p, v in self._predicates.items():
- print(" ", p, ":", v)
- return
- type_value = args.strip().split(" ")
- if len(type_value) != 2:
- print(
- f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
- )
- return
- type_, value = type_value
- if type_ not in self._predicates:
- print(f"{type_!r} not in {set(self._predicates.keys())}")
- return
- if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
- print(
- f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
- )
- return
- self._predicates[type_] = value.lower() in ("true", "yes", "1")
- if not any(self._predicates.values()):
- print(
- "Warning, all predicates set to False, skip_hidden may not have any effects."
- )
- def do_skip_hidden(self, arg):
- """
- Change whether or not we should skip frames with the
- __tracebackhide__ attribute.
- """
- if not arg.strip():
- print(
- f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
- )
- elif arg.strip().lower() in ("true", "yes"):
- self.skip_hidden = True
- elif arg.strip().lower() in ("false", "no"):
- self.skip_hidden = False
- if not any(self._predicates.values()):
- print(
- "Warning, all predicates set to False, skip_hidden may not have any effects."
- )
- def do_list(self, arg):
- """Print lines of code from the current stack frame"""
- self.lastcmd = "list"
- last = None
- if arg and arg != ".":
- try:
- x = eval(arg, {}, {})
- if type(x) == type(()):
- first, last = x
- first = int(first)
- last = int(last)
- if last < first:
- # Assume it's a count
- last = first + last
- else:
- first = max(1, int(x) - 5)
- except:
- print("*** Error in argument:", repr(arg), file=self.stdout)
- return
- elif self.lineno is None or arg == ".":
- assert self.curframe is not None
- first = max(1, self.curframe.f_lineno - 5)
- else:
- first = self.lineno + 1
- if last is None:
- last = first + 10
- assert self.curframe is not None
- self.print_list_lines(self.curframe.f_code.co_filename, first, last)
- lineno = first
- filename = self.curframe.f_code.co_filename
- self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
- do_l = do_list
- def getsourcelines(self, obj):
- lines, lineno = inspect.findsource(obj)
- if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
- # must be a module frame: do not try to cut a block out of it
- return lines, 1
- elif inspect.ismodule(obj):
- return lines, 1
- return inspect.getblock(lines[lineno:]), lineno + 1
- def do_longlist(self, arg):
- """Print lines of code from the current stack frame.
- Shows more lines than 'list' does.
- """
- self.lastcmd = "longlist"
- try:
- lines, lineno = self.getsourcelines(self.curframe)
- except OSError as err:
- self.error(str(err))
- return
- last = lineno + len(lines)
- assert self.curframe is not None
- self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
- do_ll = do_longlist
- def do_debug(self, arg):
- """debug code
- Enter a recursive debugger that steps through the code
- argument (which is an arbitrary expression or statement to be
- executed in the current environment).
- """
- trace_function = sys.gettrace()
- sys.settrace(None)
- assert self.curframe is not None
- globals = self.curframe.f_globals
- locals = self.curframe_locals
- p = self.__class__(
- completekey=self.completekey, stdin=self.stdin, stdout=self.stdout
- )
- p.use_rawinput = self.use_rawinput
- p.prompt = "(%s) " % self.prompt.strip()
- self.message("ENTERING RECURSIVE DEBUGGER")
- sys.call_tracing(p.run, (arg, globals, locals))
- self.message("LEAVING RECURSIVE DEBUGGER")
- sys.settrace(trace_function)
- self.lastcmd = p.lastcmd
- def do_pdef(self, arg):
- """Print the call signature for any callable object.
- The debugger interface to %pdef"""
- assert self.curframe is not None
- namespaces = [
- ("Locals", self.curframe_locals),
- ("Globals", self.curframe.f_globals),
- ]
- self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
- def do_pdoc(self, arg):
- """Print the docstring for an object.
- The debugger interface to %pdoc."""
- assert self.curframe is not None
- namespaces = [
- ("Locals", self.curframe_locals),
- ("Globals", self.curframe.f_globals),
- ]
- self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
- def do_pfile(self, arg):
- """Print (or run through pager) the file where an object is defined.
- The debugger interface to %pfile.
- """
- assert self.curframe is not None
- namespaces = [
- ("Locals", self.curframe_locals),
- ("Globals", self.curframe.f_globals),
- ]
- self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
- def do_pinfo(self, arg):
- """Provide detailed information about an object.
- The debugger interface to %pinfo, i.e., obj?."""
- assert self.curframe is not None
- namespaces = [
- ("Locals", self.curframe_locals),
- ("Globals", self.curframe.f_globals),
- ]
- self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
- def do_pinfo2(self, arg):
- """Provide extra detailed information about an object.
- The debugger interface to %pinfo2, i.e., obj??."""
- assert self.curframe is not None
- namespaces = [
- ("Locals", self.curframe_locals),
- ("Globals", self.curframe.f_globals),
- ]
- self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
- def do_psource(self, arg):
- """Print (or run through pager) the source code for an object."""
- assert self.curframe is not None
- namespaces = [
- ("Locals", self.curframe_locals),
- ("Globals", self.curframe.f_globals),
- ]
- self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
- def do_where(self, arg: str):
- """w(here)
- Print a stack trace, with the most recent frame at the bottom.
- An arrow indicates the "current frame", which determines the
- context of most commands. 'bt' is an alias for this command.
- Take a number as argument as an (optional) number of context line to
- print"""
- if arg:
- try:
- context = int(arg)
- except ValueError as err:
- self.error(str(err))
- return
- self.print_stack_trace(context)
- else:
- self.print_stack_trace()
- do_w = do_where
- def break_anywhere(self, frame):
- """
- _stop_in_decorator_internals is overly restrictive, as we may still want
- to trace function calls, so we need to also update break_anywhere so
- that is we don't `stop_here`, because of debugger skip, we may still
- stop at any point inside the function
- """
- sup = super().break_anywhere(frame)
- if sup:
- return sup
- if self._predicates["debuggerskip"]:
- if DEBUGGERSKIP in frame.f_code.co_varnames:
- return True
- if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
- return True
- return False
- def _is_in_decorator_internal_and_should_skip(self, frame):
- """
- Utility to tell us whether we are in a decorator internal and should stop.
- """
- # if we are disabled don't skip
- if not self._predicates["debuggerskip"]:
- return False
- return self._cachable_skip(frame)
- @lru_cache(1024)
- def _cached_one_parent_frame_debuggerskip(self, frame):
- """
- Cache looking up for DEBUGGERSKIP on parent frame.
- This should speedup walking through deep frame when one of the highest
- one does have a debugger skip.
- This is likely to introduce fake positive though.
- """
- while getattr(frame, "f_back", None):
- frame = frame.f_back
- if self._get_frame_locals(frame).get(DEBUGGERSKIP):
- return True
- return None
- @lru_cache(1024)
- def _cachable_skip(self, frame):
- # if frame is tagged, skip by default.
- if DEBUGGERSKIP in frame.f_code.co_varnames:
- return True
- # if one of the parent frame value set to True skip as well.
- if self._cached_one_parent_frame_debuggerskip(frame):
- return True
- return False
- def stop_here(self, frame):
- if self._is_in_decorator_internal_and_should_skip(frame) is True:
- return False
- hidden = False
- if self.skip_hidden:
- hidden = self._hidden_predicate(frame)
- if hidden:
- if self.report_skipped:
- print(
- self.theme.format(
- [
- (
- Token.ExcName,
- " [... skipped 1 hidden frame(s)]",
- ),
- (Token, "\n"),
- ]
- )
- )
- if self.skip and self.is_skipped_module(frame.f_globals.get("__name__", "")):
- print(
- self.theme.format(
- [
- (
- Token.ExcName,
- " [... skipped 1 ignored module(s)]",
- ),
- (Token, "\n"),
- ]
- )
- )
- return False
- return super().stop_here(frame)
- def do_up(self, arg):
- """u(p) [count]
- Move the current frame count (default one) levels up in the
- stack trace (to an older frame).
- Will skip hidden frames and ignored modules.
- """
- # modified version of upstream that skips
- # frames with __tracebackhide__ and ignored modules
- if self.curindex == 0:
- self.error("Oldest frame")
- return
- try:
- count = int(arg or 1)
- except ValueError:
- self.error("Invalid frame count (%s)" % arg)
- return
- hidden_skipped = 0
- module_skipped = 0
- if count < 0:
- _newframe = 0
- else:
- counter = 0
- hidden_frames = self.hidden_frames(self.stack)
- for i in range(self.curindex - 1, -1, -1):
- should_skip_hidden = hidden_frames[i] and self.skip_hidden
- should_skip_module = self.skip and self.is_skipped_module(
- self.stack[i][0].f_globals.get("__name__", "")
- )
- if should_skip_hidden or should_skip_module:
- if should_skip_hidden:
- hidden_skipped += 1
- if should_skip_module:
- module_skipped += 1
- continue
- counter += 1
- if counter >= count:
- break
- else:
- # if no break occurred.
- self.error(
- "all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
- )
- return
- _newframe = i
- self._select_frame(_newframe)
- total_skipped = hidden_skipped + module_skipped
- if total_skipped:
- print(
- self.theme.format(
- [
- (
- Token.ExcName,
- f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]",
- ),
- (Token, "\n"),
- ]
- )
- )
- def do_down(self, arg):
- """d(own) [count]
- Move the current frame count (default one) levels down in the
- stack trace (to a newer frame).
- Will skip hidden frames and ignored modules.
- """
- if self.curindex + 1 == len(self.stack):
- self.error("Newest frame")
- return
- try:
- count = int(arg or 1)
- except ValueError:
- self.error("Invalid frame count (%s)" % arg)
- return
- if count < 0:
- _newframe = len(self.stack) - 1
- else:
- counter = 0
- hidden_skipped = 0
- module_skipped = 0
- hidden_frames = self.hidden_frames(self.stack)
- for i in range(self.curindex + 1, len(self.stack)):
- should_skip_hidden = hidden_frames[i] and self.skip_hidden
- should_skip_module = self.skip and self.is_skipped_module(
- self.stack[i][0].f_globals.get("__name__", "")
- )
- if should_skip_hidden or should_skip_module:
- if should_skip_hidden:
- hidden_skipped += 1
- if should_skip_module:
- module_skipped += 1
- continue
- counter += 1
- if counter >= count:
- break
- else:
- self.error(
- "all frames below skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
- )
- return
- total_skipped = hidden_skipped + module_skipped
- if total_skipped:
- print(
- self.theme.format(
- [
- (
- Token.ExcName,
- f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]",
- ),
- (Token, "\n"),
- ]
- )
- )
- _newframe = i
- self._select_frame(_newframe)
- do_d = do_down
- do_u = do_up
- def _show_ignored_modules(self):
- """Display currently ignored modules."""
- if self.skip:
- print(f"Currently ignored modules: {sorted(self.skip)}")
- else:
- print("No modules are currently ignored.")
- def do_ignore_module(self, arg):
- """ignore_module <module_name>
- Add a module to the list of modules to skip when navigating frames.
- When a module is ignored, the debugger will automatically skip over
- frames from that module.
- Supports wildcard patterns using fnmatch syntax:
- Usage:
- ignore_module threading # Skip threading module frames
- ignore_module asyncio.\\* # Skip all asyncio submodules
- ignore_module \\*.tests # Skip all test modules
- ignore_module # List currently ignored modules
- """
- if self.skip is None:
- self.skip = set()
- module_name = arg.strip()
- if not module_name:
- self._show_ignored_modules()
- return
- self.skip.add(module_name)
- def do_unignore_module(self, arg):
- """unignore_module <module_name>
- Remove a module from the list of modules to skip when navigating frames.
- This will allow the debugger to step into frames from the specified module.
- Usage:
- unignore_module threading # Stop ignoring threading module frames
- unignore_module asyncio.\\* # Remove asyncio.* pattern
- unignore_module # List currently ignored modules
- """
- if self.skip is None:
- self.skip = set()
- module_name = arg.strip()
- if not module_name:
- self._show_ignored_modules()
- return
- try:
- self.skip.remove(module_name)
- except KeyError:
- print(f"Module {module_name} is not currently ignored")
- self._show_ignored_modules()
- def do_context(self, context: str):
- """context number_of_lines
- Set the number of lines of source code to show when displaying
- stacktrace information.
- """
- try:
- new_context = int(context)
- if new_context <= 0:
- raise ValueError()
- self.context = new_context
- except ValueError:
- self.error(
- f"The 'context' command requires a positive integer argument (current value {self.context})."
- )
- class InterruptiblePdb(Pdb):
- """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
- def cmdloop(self, intro=None):
- """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
- try:
- return OldPdb.cmdloop(self, intro=intro)
- except KeyboardInterrupt:
- self.stop_here = lambda frame: False # type: ignore[method-assign]
- self.do_quit("")
- sys.settrace(None)
- self.quitting = False
- raise
- def _cmdloop(self):
- while True:
- try:
- # keyboard interrupts allow for an easy way to cancel
- # the current command, so allow them during interactive input
- self.allow_kbdint = True
- self.cmdloop()
- self.allow_kbdint = False
- break
- except KeyboardInterrupt:
- self.message("--KeyboardInterrupt--")
- raise
- def set_trace(frame=None, header=None):
- """
- Start debugging from `frame`.
- If frame is not specified, debugging starts from caller's frame.
- """
- pdb = Pdb()
- if header is not None:
- pdb.message(header)
- pdb.set_trace(frame or sys._getframe().f_back)
|