Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Doc/library/pdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,17 @@ can be overridden by the local file.
Move the current frame *count* (default one) levels down in the stack trace
(to a newer frame).

Frames from ignored modules will be skipped. Use :pdbcmd:`ignore_module` to
ignore modules and :pdbcmd:`unignore_module` to stop ignoring them.

.. pdbcommand:: u(p) [count]

Move the current frame *count* (default one) levels up in the stack trace (to
an older frame).

Frames from ignored modules will be skipped. Use :pdbcmd:`ignore_module` to
ignore modules and :pdbcmd:`unignore_module` to stop ignoring them.

.. pdbcommand:: b(reak) [([filename:]lineno | function) [, condition]]

With a *lineno* argument, set a break at line *lineno* in the current file.
Expand Down Expand Up @@ -718,6 +724,47 @@ can be overridden by the local file.
:pdbcmd:`interact` directs its output to the debugger's
output channel rather than :data:`sys.stderr`.

.. pdbcommand:: ignore_module [module_name]

Add a module to the list of modules to skip when stepping, continuing, or
navigating frames. When a module is ignored, the debugger will automatically
skip over frames from that module during :pdbcmd:`step`, :pdbcmd:`next`,
:pdbcmd:`continue`, :pdbcmd:`up`, and :pdbcmd:`down` commands.

Supports wildcard patterns using glob-style matching (via :mod:`fnmatch`).

Without *module_name*, list the currently ignored modules.

Examples::

(Pdb) ignore_module threading # Skip threading module frames
(Pdb) ignore_module asyncio.* # Skip all asyncio submodules
(Pdb) ignore_module *.tests # Skip all test modules
(Pdb) ignore_module # List currently ignored modules

Common use cases:

- Skip framework code (``django.*``, ``flask.*``, ``asyncio.*``)
- Skip standard library modules (``threading``, ``multiprocessing``, ``logging.*``)
- Skip test framework internals (``*pytest*``, ``unittest.*``)

.. versionadded:: 3.15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. versionadded:: 3.15
.. versionadded:: next


.. pdbcommand:: unignore_module [module_name]

Remove a module from the list of modules to skip when stepping or navigating
frames. This will allow the debugger to step into frames from the specified
module again.

Without *module_name*, list the currently ignored modules.

Example::

(Pdb) unignore_module threading # Stop ignoring threading module frames
(Pdb) unignore_module asyncio.* # Remove the asyncio.* pattern

.. versionadded:: 3.15

.. _debugger-aliases:

.. pdbcommand:: alias [name [command]]
Expand Down
2 changes: 1 addition & 1 deletion Doc/tools/extensions/pyspecific.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def parse_opcode_signature(env, sig, signode):

# Support for documenting pdb commands

pdbcmd_sig_re = re.compile(r'([a-z()!]+)\s*(.*)')
pdbcmd_sig_re = re.compile(r'([a-z()!_]+)\s*(.*)')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commands usually have dashes instead of underscores. At least most UNIX commands would be with dashes so I think it's better to have dashes in general. (also gdb uses dashes and not underscores for commands with multiple words)

Copy link
Author

@ktowen ktowen Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to follow the convention of using snake case, the other option I was thinking is not using separator as pdb does with the commands longlist, whatis and retval. Adding dashes will imply changing how the commands are defined and discovered.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For CLI-like options, I would prefer using dashes. Those are not really variables (even though they are stored in variables in the end). I'll leave the decision to Tian but we could also consider not having dashes/underscores at all if we find better words (showmodule, hidemodule would be fine I think although we're not really hiding them per se, just hiding them in the traceback).


# later...
# pdbargs_tokens_re = re.compile(r'''[a-zA-Z]+ | # identifiers
Expand Down
124 changes: 122 additions & 2 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,13 @@ def curframe_locals(self, value):

# Override Bdb methods

def stop_here(self, frame):
"""Override bdb's stop_here to add message when skipping ignored modules."""
if self.skip and self.is_skipped_module(frame.f_globals.get('__name__', '')):
self.message('[... skipped 1 ignored module(s)]')
return False
return super().stop_here(frame)

def user_call(self, frame, argument_list):
"""This method is called when there is the remote possibility
that we ever need to stop in this function."""
Expand Down Expand Up @@ -1774,6 +1781,8 @@ def do_up(self, arg):

Move the current frame count (default one) levels up in the
stack trace (to an older frame).

Will skip frames from ignored modules.
"""
if self.curindex == 0:
self.error('Oldest frame')
Expand All @@ -1786,7 +1795,30 @@ def do_up(self, arg):
if count < 0:
newframe = 0
else:
newframe = max(0, self.curindex - count)
# Skip over ignored modules
counter = 0
module_skipped = 0
for i in range(self.curindex - 1, -1, -1):
frame = self.stack[i][0]
should_skip_module = (self.skip and
self.is_skipped_module(frame.f_globals.get('__name__', '')))

if should_skip_module:
module_skipped += 1
continue
counter += 1
if counter >= count:
break
else:
# No valid frames found
self.error('All frames above are from ignored modules. '
'Use "unignore_module" to allow stepping into them.')
return

newframe = i
if module_skipped:
self.message(f'[... skipped {module_skipped} frame(s) '
'from ignored modules]')
self._select_frame(newframe)
do_u = do_up

Expand All @@ -1795,6 +1827,8 @@ def do_down(self, arg):

Move the current frame count (default one) levels down in the
stack trace (to a newer frame).

Will skip frames from ignored modules.
"""
if self.curindex + 1 == len(self.stack):
self.error('Newest frame')
Expand All @@ -1807,7 +1841,30 @@ def do_down(self, arg):
if count < 0:
newframe = len(self.stack) - 1
else:
newframe = min(len(self.stack) - 1, self.curindex + count)
# Skip over ignored modules
counter = 0
module_skipped = 0
for i in range(self.curindex + 1, len(self.stack)):
frame = self.stack[i][0]
should_skip_module = (self.skip and
self.is_skipped_module(frame.f_globals.get('__name__', '')))

if should_skip_module:
module_skipped += 1
continue
counter += 1
if counter >= count:
break
else:
# No valid frames found
self.error('All frames below are from ignored modules. '
'Use "unignore_module" to allow stepping into them.')
return

newframe = i
if module_skipped:
self.message(f'[... skipped {module_skipped} frame(s) '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest indicating which module was ignored if there is only 1 module being ignored.

'from ignored modules]')
self._select_frame(newframe)
do_d = do_down

Expand Down Expand Up @@ -2327,6 +2384,69 @@ def do_interact(self, arg):
console.interact(banner="*pdb interact start*",
exitmsg="*exit from pdb interact command*")

def _show_ignored_modules(self):
"""Display currently ignored modules."""
if self.skip:
self.message(f'Currently ignored modules: {sorted(self.skip)}')
else:
self.message('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 stepping,
continuing, or navigating frames. When a module is ignored,
the debugger will automatically skip over frames from that
module during step, next, continue, up, and down commands.

Supports wildcard patterns using glob-style matching:

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)
self.message(f'Ignoring module: {module_name}')

def do_unignore_module(self, arg):
"""unignore_module [module_name]

Remove a module from the list of modules to skip when stepping
or 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)
self.message(f'No longer ignoring module: {module_name}')
except KeyError:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be emitted after .remove was called (in case .message raises a KeyError for some obscure reasons)

self.error(f'Module {module_name} is not currently ignored')
self._show_ignored_modules()

def do_alias(self, arg):
"""alias [name [command]]

Expand Down
Loading
Loading