Skip to content
Merged
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
38 changes: 18 additions & 20 deletions ipykernel/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,15 @@ class Debugger:
]

def __init__(
self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True
self,
log,
debugpy_stream,
event_callback,
shell_socket,
session,
kernel_modules,
just_my_code=False,
filter_internal_frames=True,
):
"""Initialize the debugger."""
self.log = log
Expand All @@ -335,7 +343,9 @@ def __init__(
self.session = session
self.is_started = False
self.event_callback = event_callback
self.kernel_modules = kernel_modules
self.just_my_code = just_my_code
self.filter_internal_frames = filter_internal_frames
self.stopped_queue: Queue[t.Any] = Queue()

self.started_debug_handlers = {}
Expand Down Expand Up @@ -498,25 +508,7 @@ async def source(self, message):

async def stackTrace(self, message):
"""Handle a stack trace message."""
reply = await self._forward_message(message)
# The stackFrames array can have the following content:
# { frames from the notebook}
# ...
# { 'id': xxx, 'name': '<module>', ... } <= this is the first frame of the code from the notebook
# { frames from ipykernel }
# ...
# {'id': yyy, 'name': '<module>', ... } <= this is the first frame of ipykernel code
# or only the frames from the notebook.
# We want to remove all the frames from ipykernel when they are present.
try:
sf_list = reply["body"]["stackFrames"]
module_idx = len(sf_list) - next(
i for i, v in enumerate(reversed(sf_list), 1) if v["name"] == "<module>" and i != 1
)
reply["body"]["stackFrames"] = reply["body"]["stackFrames"][: module_idx + 1]
except StopIteration:
pass
return reply
return await self._forward_message(message)

def accept_variable(self, variable_name):
"""Accept a variable by name."""
Expand Down Expand Up @@ -574,6 +566,12 @@ async def attach(self, message):
# Set debugOptions for breakpoints in python standard library source.
if not self.just_my_code:
message["arguments"]["debugOptions"] = ["DebugStdLib"]

# Dynamic skip rules (computed at kernel startup)
if self.filter_internal_frames:
rules = [{"path": path, "include": False} for path in self.kernel_modules]
message["arguments"]["rules"] = rules

return await self._forward_message(message)

async def configurationDone(self, message):
Expand Down
6 changes: 6 additions & 0 deletions ipykernel/ipkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def __init__(self, **kwargs):

from .debugger import _is_debugpy_available

self._kernel_modules = [
m.__file__ for m in sys.modules.values() if hasattr(m, "__file__") and m.__file__
]

# Initialize the Debugger
if _is_debugpy_available:
self.debugger = self.debugger_class(
Expand All @@ -127,7 +131,9 @@ def __init__(self, **kwargs):
self._publish_debug_event,
self.debug_shell_socket,
self.session,
self._kernel_modules,
self.debug_just_my_code,
self.filter_internal_frames,
)

# Initialize the InteractiveShell subclass
Expand Down
9 changes: 8 additions & 1 deletion ipykernel/kernelbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,18 @@ def _default_ident(self):
# The ipykernel source is in the call stack, so the user
# has to manipulate the step-over and step-into in a wize way.
debug_just_my_code = Bool(
True,
False,
help="""Set to False if you want to debug python standard and dependent libraries.
""",
).tag(config=True)

# Experimental option to filter internal frames from the stack trace and stepping.
filter_internal_frames = Bool(
True,
help="""Set to False if you want to debug kernel modules.
""",
).tag(config=True)

# track associations with current request
# Private interface

Expand Down