Skip to content

Commit 5bda9c0

Browse files
Add copy_to_globals debug request handling (#1055)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 601c0fe commit 5bda9c0

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed

ipykernel/debugger.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,13 @@ class Debugger:
316316
]
317317

318318
# Requests that can be handled even if the debugger is not running
319-
static_debug_msg_types = ["debugInfo", "inspectVariables", "richInspectVariables", "modules"]
319+
static_debug_msg_types = [
320+
"debugInfo",
321+
"inspectVariables",
322+
"richInspectVariables",
323+
"modules",
324+
"copyToGlobals",
325+
]
320326

321327
def __init__(
322328
self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True
@@ -662,6 +668,26 @@ async def richInspectVariables(self, message):
662668
reply["success"] = True
663669
return reply
664670

671+
async def copyToGlobals(self, message):
672+
dst_var_name = message["arguments"]["dstVariableName"]
673+
src_var_name = message["arguments"]["srcVariableName"]
674+
src_frame_id = message["arguments"]["srcFrameId"]
675+
676+
expression = f"globals()['{dst_var_name}']"
677+
seq = message["seq"]
678+
return await self._forward_message(
679+
{
680+
"type": "request",
681+
"command": "setExpression",
682+
"seq": seq + 1,
683+
"arguments": {
684+
"expression": expression,
685+
"value": src_var_name,
686+
"frameId": src_frame_id,
687+
},
688+
}
689+
)
690+
665691
async def modules(self, message):
666692
"""Handle a modules message."""
667693
modules = list(sys.modules.values())

ipykernel/tests/test_debugger.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,100 @@ def test_convert_to_long_pathname():
282282
from ipykernel.compiler import _convert_to_long_pathname
283283

284284
_convert_to_long_pathname(__file__)
285+
286+
287+
def test_copy_to_globals(kernel_with_debug):
288+
local_var_name = "var"
289+
global_var_name = "var_copy"
290+
code = f"""from IPython.core.display import HTML
291+
def my_test():
292+
{local_var_name} = HTML('<p>test content</p>')
293+
pass
294+
a = 2
295+
my_test()"""
296+
297+
# Init debugger and set breakpoint
298+
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
299+
source = r["body"]["sourcePath"]
300+
301+
wait_for_debug_request(
302+
kernel_with_debug,
303+
"setBreakpoints",
304+
{
305+
"breakpoints": [{"line": 4}],
306+
"source": {"path": source},
307+
"sourceModified": False,
308+
},
309+
)
310+
311+
wait_for_debug_request(kernel_with_debug, "debugInfo")
312+
313+
wait_for_debug_request(kernel_with_debug, "configurationDone")
314+
315+
# Execute code
316+
kernel_with_debug.execute(code)
317+
318+
# Wait for stop on breakpoint
319+
msg: dict = {"msg_type": "", "content": {}}
320+
while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped":
321+
msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT)
322+
323+
stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][
324+
"stackFrames"
325+
]
326+
327+
# Get local frame id
328+
frame_id = stacks[0]["id"]
329+
330+
# Copy the variable
331+
wait_for_debug_request(
332+
kernel_with_debug,
333+
"copyToGlobals",
334+
{
335+
"srcVariableName": local_var_name,
336+
"dstVariableName": global_var_name,
337+
"srcFrameId": frame_id,
338+
},
339+
)
340+
341+
# Get the scopes
342+
scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": frame_id})["body"][
343+
"scopes"
344+
]
345+
346+
# Get the local variable
347+
locals_ = wait_for_debug_request(
348+
kernel_with_debug,
349+
"variables",
350+
{
351+
"variablesReference": next(filter(lambda s: s["name"] == "Locals", scopes))[
352+
"variablesReference"
353+
]
354+
},
355+
)["body"]["variables"]
356+
357+
local_var = None
358+
for variable in locals_:
359+
if local_var_name in variable["evaluateName"]:
360+
local_var = variable
361+
assert local_var is not None
362+
363+
# Get the global variable (copy of the local variable)
364+
globals_ = wait_for_debug_request(
365+
kernel_with_debug,
366+
"variables",
367+
{
368+
"variablesReference": next(filter(lambda s: s["name"] == "Globals", scopes))[
369+
"variablesReference"
370+
]
371+
},
372+
)["body"]["variables"]
373+
374+
global_var = None
375+
for variable in globals_:
376+
if global_var_name in variable["evaluateName"]:
377+
global_var = variable
378+
assert global_var is not None
379+
380+
# Compare local and global variable
381+
assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ classifiers = [
2525
urls = {Homepage = "https://ipython.org"}
2626
requires-python = ">=3.8"
2727
dependencies = [
28-
"debugpy>=1.0",
28+
"debugpy>=1.6.5",
2929
"ipython>=7.23.1",
3030
"comm>=0.1.1",
3131
"traitlets>=5.4.0",

0 commit comments

Comments
 (0)