Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cc81ca1

Browse files
authored
Add module ignoring functionality to debugger (#14973)
This PR adds two new commands to ignore (and unignore) specific modules when navigating frames in the IPython debugger, similar to how the debugger already skips hidden frames. It will work with all the commands that implies moving between frames like `up`, `down`, `step` and `next`. It is useful when you don't want the debugger to enter third party code. Closes #1665
2 parents fb1fdbf + 0cb9d18 commit cc81ca1

4 files changed

Lines changed: 489 additions & 17 deletions

File tree

IPython/core/debugger.py

Lines changed: 116 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,17 +1107,32 @@ def stop_here(self, frame):
11071107
]
11081108
)
11091109
)
1110+
if self.skip and self.is_skipped_module(frame.f_globals.get("__name__", "")):
1111+
print(
1112+
self.theme.format(
1113+
[
1114+
(
1115+
Token.ExcName,
1116+
" [... skipped 1 ignored module(s)]",
1117+
),
1118+
(Token, "\n"),
1119+
]
1120+
)
1121+
)
1122+
1123+
return False
1124+
11101125
return super().stop_here(frame)
11111126

11121127
def do_up(self, arg):
11131128
"""u(p) [count]
11141129
Move the current frame count (default one) levels up in the
11151130
stack trace (to an older frame).
11161131
1117-
Will skip hidden frames.
1132+
Will skip hidden frames and ignored modules.
11181133
"""
11191134
# modified version of upstream that skips
1120-
# frames with __tracebackhide__
1135+
# frames with __tracebackhide__ and ignored modules
11211136
if self.curindex == 0:
11221137
self.error("Oldest frame")
11231138
return
@@ -1126,35 +1141,49 @@ def do_up(self, arg):
11261141
except ValueError:
11271142
self.error("Invalid frame count (%s)" % arg)
11281143
return
1129-
skipped = 0
1144+
1145+
hidden_skipped = 0
1146+
module_skipped = 0
1147+
11301148
if count < 0:
11311149
_newframe = 0
11321150
else:
11331151
counter = 0
11341152
hidden_frames = self.hidden_frames(self.stack)
1153+
11351154
for i in range(self.curindex - 1, -1, -1):
1136-
if hidden_frames[i] and self.skip_hidden:
1137-
skipped += 1
1155+
should_skip_hidden = hidden_frames[i] and self.skip_hidden
1156+
should_skip_module = self.skip and self.is_skipped_module(
1157+
self.stack[i][0].f_globals.get("__name__", "")
1158+
)
1159+
1160+
if should_skip_hidden or should_skip_module:
1161+
if should_skip_hidden:
1162+
hidden_skipped += 1
1163+
if should_skip_module:
1164+
module_skipped += 1
11381165
continue
11391166
counter += 1
11401167
if counter >= count:
11411168
break
11421169
else:
11431170
# if no break occurred.
11441171
self.error(
1145-
"all frames above hidden, use `skip_hidden False` to get get into those."
1172+
"all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
11461173
)
11471174
return
11481175

11491176
_newframe = i
11501177
self._select_frame(_newframe)
1151-
if skipped:
1178+
1179+
total_skipped = hidden_skipped + module_skipped
1180+
if total_skipped:
11521181
print(
11531182
self.theme.format(
11541183
[
11551184
(
11561185
Token.ExcName,
1157-
f" [... skipped {skipped} hidden frame(s)]",
1186+
f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]",
11581187
),
11591188
(Token, "\n"),
11601189
]
@@ -1166,7 +1195,7 @@ def do_down(self, arg):
11661195
Move the current frame count (default one) levels down in the
11671196
stack trace (to a newer frame).
11681197
1169-
Will skip hidden frames.
1198+
Will skip hidden frames and ignored modules.
11701199
"""
11711200
if self.curindex + 1 == len(self.stack):
11721201
self.error("Newest frame")
@@ -1180,28 +1209,39 @@ def do_down(self, arg):
11801209
_newframe = len(self.stack) - 1
11811210
else:
11821211
counter = 0
1183-
skipped = 0
1212+
hidden_skipped = 0
1213+
module_skipped = 0
11841214
hidden_frames = self.hidden_frames(self.stack)
1215+
11851216
for i in range(self.curindex + 1, len(self.stack)):
1186-
if hidden_frames[i] and self.skip_hidden:
1187-
skipped += 1
1217+
should_skip_hidden = hidden_frames[i] and self.skip_hidden
1218+
should_skip_module = self.skip and self.is_skipped_module(
1219+
self.stack[i][0].f_globals.get("__name__", "")
1220+
)
1221+
1222+
if should_skip_hidden or should_skip_module:
1223+
if should_skip_hidden:
1224+
hidden_skipped += 1
1225+
if should_skip_module:
1226+
module_skipped += 1
11881227
continue
11891228
counter += 1
11901229
if counter >= count:
11911230
break
11921231
else:
11931232
self.error(
1194-
"all frames below hidden, use `skip_hidden False` to get get into those."
1233+
"all frames below skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
11951234
)
11961235
return
11971236

1198-
if skipped:
1237+
total_skipped = hidden_skipped + module_skipped
1238+
if total_skipped:
11991239
print(
12001240
self.theme.format(
12011241
[
12021242
(
12031243
Token.ExcName,
1204-
f" [... skipped {skipped} hidden frame(s)]",
1244+
f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]",
12051245
),
12061246
(Token, "\n"),
12071247
]
@@ -1214,6 +1254,67 @@ def do_down(self, arg):
12141254
do_d = do_down
12151255
do_u = do_up
12161256

1257+
def _show_ignored_modules(self):
1258+
"""Display currently ignored modules."""
1259+
if self.skip:
1260+
print(f"Currently ignored modules: {sorted(self.skip)}")
1261+
else:
1262+
print("No modules are currently ignored.")
1263+
1264+
def do_ignore_module(self, arg):
1265+
"""ignore_module <module_name>
1266+
1267+
Add a module to the list of modules to skip when navigating frames.
1268+
When a module is ignored, the debugger will automatically skip over
1269+
frames from that module.
1270+
1271+
Supports wildcard patterns using fnmatch syntax:
1272+
1273+
Usage:
1274+
ignore_module threading # Skip threading module frames
1275+
ignore_module asyncio.\\* # Skip all asyncio submodules
1276+
ignore_module \\*.tests # Skip all test modules
1277+
ignore_module # List currently ignored modules
1278+
"""
1279+
1280+
if self.skip is None:
1281+
self.skip = set()
1282+
1283+
module_name = arg.strip()
1284+
1285+
if not module_name:
1286+
self._show_ignored_modules()
1287+
return
1288+
1289+
self.skip.add(module_name)
1290+
1291+
def do_unignore_module(self, arg):
1292+
"""unignore_module <module_name>
1293+
1294+
Remove a module from the list of modules to skip when navigating frames.
1295+
This will allow the debugger to step into frames from the specified module.
1296+
1297+
Usage:
1298+
unignore_module threading # Stop ignoring threading module frames
1299+
unignore_module asyncio.\\* # Remove asyncio.* pattern
1300+
unignore_module # List currently ignored modules
1301+
"""
1302+
1303+
if self.skip is None:
1304+
self.skip = set()
1305+
1306+
module_name = arg.strip()
1307+
1308+
if not module_name:
1309+
self._show_ignored_modules()
1310+
return
1311+
1312+
try:
1313+
self.skip.remove(module_name)
1314+
except KeyError:
1315+
print(f"Module {module_name} is not currently ignored")
1316+
self._show_ignored_modules()
1317+
12171318
def do_context(self, context: str):
12181319
"""context number_of_lines
12191320
Set the number of lines of source code to show when displaying

docs/source/interactive/reference.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,65 @@ And less context on shallower Stack Trace:
820820
<ipython-input-5-7baadc3d1465>(5)foo()
821821
----> 5 return 1/x+foo(x-1)
822822
823+
Module Ignoring Commands
824+
------------------------
825+
826+
IPython's debugger provides commands to ignore specific modules when navigating
827+
through stack frames. This is particularly useful when debugging applications
828+
that use frameworks or libraries where you want to skip over their internal
829+
code and focus on your application logic.
830+
831+
The module ignoring system supports wildcard patterns using Python's ``fnmatch``
832+
syntax, allowing you to ignore groups of related modules with a single pattern.
833+
834+
ignore_module <module_name>
835+
++++++++++++++++++++++++++++
836+
837+
Add a module to the list of modules to skip when navigating frames. When a module
838+
is ignored, the debugger will automatically skip over frames from that module when
839+
using the ``next``, ``step``, ``continue``, ``up`` and ``down`` commands.
840+
841+
**Usage:**
842+
843+
.. code::
844+
845+
ipdb> ignore_module threading
846+
ipdb> ignore_module __main__
847+
ipdb> ignore_module asyncio.* # Ignore all asyncio submodules
848+
ipdb> ignore_module *.tests # Ignore all test modules
849+
ipdb> ignore_module django.* # Ignore all django modules
850+
ipdb> ignore_module # List currently ignored modules
851+
Currently ignored modules: ['__main__', 'asyncio.*', 'django.*', 'threading']
852+
853+
**Wildcard Pattern Examples:**
854+
855+
- ``asyncio.*`` - Matches ``asyncio.tasks``, ``asyncio.events``, etc.
856+
- ``*.tests`` - Matches ``myapp.tests``, ``utils.tests``, etc.
857+
- ``test_*`` - Matches ``test_models``, ``test_views``, etc.
858+
- ``django.*`` - Matches all Django framework modules
859+
- ``*pytest*`` - Matches any module containing "pytest"
860+
861+
**Common use cases:**
862+
863+
- Ignore framework code (e.g., ``django.*``, ``flask.*``, ``asyncio.*``)
864+
- Skip standard library modules (e.g., ``threading``, ``multiprocessing``, ``logging.*``)
865+
- Hide main module frames (``__main__``) to focus on function implementations
866+
- Skip test framework internals (``*pytest*``, ``unittest.*``)
867+
868+
unignore_module <module_name>
869+
+++++++++++++++++++++++++++++
870+
871+
Remove a module from the list of modules to skip when navigating frames. This
872+
allows the debugger to step into frames from the specified module again.
873+
874+
**Usage:**
875+
876+
.. code::
877+
878+
ipdb> unignore_module threading
879+
ipdb> unignore_module asyncio.* # Remove the pattern
880+
ipdb> unignore_module # List currently ignored modules
881+
Currently ignored modules: ['__main__']
823882
824883
Post-mortem debugging
825884
---------------------

tests/test_debug_magic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def test_debug_magic_passes_through_generators():
7575
child.expect(ipdb_prompt)
7676
child.sendline("u")
7777
child.expect_exact(
78-
"*** all frames above hidden, use `skip_hidden False` to get get into those."
78+
"*** all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules."
7979
)
8080

8181
child.expect(ipdb_prompt)

0 commit comments

Comments
 (0)