diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 43ae68300..b59ad476f 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -316,7 +316,13 @@ class Debugger: ] # Requests that can be handled even if the debugger is not running - static_debug_msg_types = ["debugInfo", "inspectVariables", "richInspectVariables", "modules"] + static_debug_msg_types = [ + "debugInfo", + "inspectVariables", + "richInspectVariables", + "modules", + "copyToGlobals", + ] def __init__( self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True @@ -662,6 +668,26 @@ async def richInspectVariables(self, message): reply["success"] = True return reply + async def copyToGlobals(self, message): + dst_var_name = message["arguments"]["dstVariableName"] + src_var_name = message["arguments"]["srcVariableName"] + src_frame_id = message["arguments"]["srcFrameId"] + + expression = f"globals()['{dst_var_name}']" + seq = message["seq"] + return await self._forward_message( + { + "type": "request", + "command": "setExpression", + "seq": seq + 1, + "arguments": { + "expression": expression, + "value": src_var_name, + "frameId": src_frame_id, + }, + } + ) + async def modules(self, message): """Handle a modules message.""" modules = list(sys.modules.values()) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 200154cfc..c9071296c 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -282,3 +282,100 @@ def test_convert_to_long_pathname(): from ipykernel.compiler import _convert_to_long_pathname _convert_to_long_pathname(__file__) + + +def test_copy_to_globals(kernel_with_debug): + local_var_name = "var" + global_var_name = "var_copy" + code = f"""from IPython.core.display import HTML +def my_test(): + {local_var_name} = HTML('
test content
') + pass +a = 2 +my_test()""" + + # Init debugger and set breakpoint + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + source = r["body"]["sourcePath"] + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 4}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "debugInfo") + + wait_for_debug_request(kernel_with_debug, "configurationDone") + + # Execute code + kernel_with_debug.execute(code) + + # Wait for stop on breakpoint + msg: dict = {"msg_type": "", "content": {}} + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][ + "stackFrames" + ] + + # Get local frame id + frame_id = stacks[0]["id"] + + # Copy the variable + wait_for_debug_request( + kernel_with_debug, + "copyToGlobals", + { + "srcVariableName": local_var_name, + "dstVariableName": global_var_name, + "srcFrameId": frame_id, + }, + ) + + # Get the scopes + scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": frame_id})["body"][ + "scopes" + ] + + # Get the local variable + locals_ = wait_for_debug_request( + kernel_with_debug, + "variables", + { + "variablesReference": next(filter(lambda s: s["name"] == "Locals", scopes))[ + "variablesReference" + ] + }, + )["body"]["variables"] + + local_var = None + for variable in locals_: + if local_var_name in variable["evaluateName"]: + local_var = variable + assert local_var is not None + + # Get the global variable (copy of the local variable) + globals_ = wait_for_debug_request( + kernel_with_debug, + "variables", + { + "variablesReference": next(filter(lambda s: s["name"] == "Globals", scopes))[ + "variablesReference" + ] + }, + )["body"]["variables"] + + global_var = None + for variable in globals_: + if global_var_name in variable["evaluateName"]: + global_var = variable + assert global_var is not None + + # Compare local and global variable + assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] diff --git a/pyproject.toml b/pyproject.toml index 1730197a0..ef9587f89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ urls = {Homepage = "https://ipython.org"} requires-python = ">=3.8" dependencies = [ - "debugpy>=1.0", + "debugpy>=1.6.5", "ipython>=7.23.1", "comm>=0.1.1", "traitlets>=5.4.0",