Skip to content

Commit e8d4f66

Browse files
author
Steven Silvester
authored
Merge pull request #793 from fcollonval/fix/rich-inspect
Fix rich variables inspection
2 parents 46e6e32 + 3734ec6 commit e8d4f66

File tree

4 files changed

+238
-45
lines changed

4 files changed

+238
-45
lines changed

.coveragerc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
omit = ipykernel/tests/*

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ global-exclude .ipynb_checkpoints
2323
prune data_kernelspec
2424
exclude .mailmap
2525
exclude readthedocs.yml
26+
27+
exclude .coveragerc

ipykernel/debugger.py

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -511,66 +511,53 @@ async def inspectVariables(self, message):
511511

512512
async def richInspectVariables(self, message):
513513
reply = {
514-
'type': 'response',
515-
'sequence_seq': message['seq'],
516-
'success': False,
517-
'command': message['command']
514+
"type": "response",
515+
"sequence_seq": message["seq"],
516+
"success": False,
517+
"command": message["command"],
518518
}
519-
520-
var_name = message['arguments']['variableName']
519+
520+
var_name = message["arguments"]["variableName"]
521521
valid_name = str.isidentifier(var_name)
522522
if not valid_name:
523-
reply['body'] = {
524-
'data': {},
525-
'metadata': {}
526-
}
527-
if var_name == 'special variables' or var_name == 'function variables':
528-
reply['success'] = True
523+
reply["body"] = {"data": {}, "metadata": {}}
524+
if var_name == "special variables" or var_name == "function variables":
525+
reply["success"] = True
529526
return reply
530527

531-
var_repr_data = var_name + '_repr_data'
532-
var_repr_metadata = var_name + '_repr_metadata'
533-
534-
if not self.breakpoint_list:
528+
repr_data = {}
529+
repr_metadata = {}
530+
if not self.stopped_threads:
535531
# The code did not hit a breakpoint, we use the intepreter
536532
# to get the rich representation of the variable
537-
var_repr_data, var_repr_metadata = get_ipython().display_formatter.format(var_name)
533+
result = get_ipython().user_expressions({var_name: var_name})[var_name]
534+
if result.get("status", "error") == "ok":
535+
repr_data = result.get("data", {})
536+
repr_metadata = result.get("metadata", {})
538537
else:
539538
# The code has stopped on a breakpoint, we use the setExpression
540539
# request to get the rich representation of the variable
541-
lvalue = var_repr_data + ',' + var_repr_metadata
542-
code = 'get_ipython().display_formatter.format(' + var_name+')'
543-
frame_id = message['arguments']['frameId']
544-
seq = message['seq']
545-
request = {
546-
'type': 'request',
547-
'command': 'setExpression',
548-
'seq': seq+1,
549-
'arguments': {
550-
'expression': lvalue,
551-
'value': code,
552-
'frameId': frame_id
540+
code = "get_ipython().display_formatter.format(" + var_name + ")"
541+
frame_id = message["arguments"]["frameId"]
542+
seq = message["seq"]
543+
reply = await self._forward_message(
544+
{
545+
"type": "request",
546+
"command": "evaluate",
547+
"seq": seq + 1,
548+
"arguments": {"expression": code, "frameId": frame_id},
553549
}
554-
}
555-
await self._forward_message(request)
550+
)
551+
if reply["success"]:
552+
repr_data, repr_metadata = eval(reply["body"]["result"], {}, {})
556553

557-
repr_data = globals()[var_repr_data]
558-
repr_metadata = globals()[var_repr_metadata]
559554
body = {
560-
'data': {},
561-
'metadata': {}
555+
"data": repr_data,
556+
"metadata": {k: v for k, v in repr_metadata.items() if k in repr_data},
562557
}
563558

564-
for key, value in repr_data.items():
565-
body['data']['key'] = value
566-
if key in repr_metadata:
567-
body['metadata'][key] = repr_metadata[key]
568-
569-
globals().pop(var_repr_data)
570-
globals().pop(var_repr_metadata)
571-
572-
reply['body'] = body
573-
reply['success'] = True
559+
reply["body"] = body
560+
reply["success"] = True
574561
return reply
575562

576563
async def process_request(self, message):

ipykernel/tests/test_debugger.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import pytest
2+
3+
from .utils import new_kernel, get_reply
4+
5+
seq = 0
6+
7+
# Skip if debugpy is not available
8+
pytest.importorskip("debugpy")
9+
10+
11+
def wait_for_debug_request(kernel, command, arguments=None):
12+
"""Carry out a debug request and return the reply content.
13+
14+
It does not check if the request was successful.
15+
"""
16+
global seq
17+
seq += 1
18+
19+
msg = kernel.session.msg(
20+
"debug_request",
21+
{
22+
"type": "request",
23+
"seq": seq,
24+
"command": command,
25+
"arguments": arguments or {},
26+
},
27+
)
28+
kernel.control_channel.send(msg)
29+
reply = get_reply(kernel, msg["header"]["msg_id"], channel="control")
30+
return reply["content"]
31+
32+
33+
@pytest.fixture
34+
def kernel():
35+
with new_kernel() as kc:
36+
yield kc
37+
38+
39+
@pytest.fixture
40+
def kernel_with_debug(kernel):
41+
# Initialize
42+
wait_for_debug_request(
43+
kernel,
44+
"initialize",
45+
{
46+
"clientID": "test-client",
47+
"clientName": "testClient",
48+
"adapterID": "",
49+
"pathFormat": "path",
50+
"linesStartAt1": True,
51+
"columnsStartAt1": True,
52+
"supportsVariableType": True,
53+
"supportsVariablePaging": True,
54+
"supportsRunInTerminalRequest": True,
55+
"locale": "en",
56+
},
57+
)
58+
59+
# Attach
60+
wait_for_debug_request(kernel, "attach")
61+
62+
try:
63+
yield kernel
64+
finally:
65+
# Detach
66+
wait_for_debug_request(
67+
kernel, "disconnect", {"restart": False, "terminateDebuggee": True}
68+
)
69+
70+
71+
def test_debug_initialize(kernel):
72+
reply = wait_for_debug_request(
73+
kernel,
74+
"initialize",
75+
{
76+
"clientID": "test-client",
77+
"clientName": "testClient",
78+
"adapterID": "",
79+
"pathFormat": "path",
80+
"linesStartAt1": True,
81+
"columnsStartAt1": True,
82+
"supportsVariableType": True,
83+
"supportsVariablePaging": True,
84+
"supportsRunInTerminalRequest": True,
85+
"locale": "en",
86+
},
87+
)
88+
assert reply["success"]
89+
90+
91+
def test_attach_debug(kernel_with_debug):
92+
reply = wait_for_debug_request(
93+
kernel_with_debug, "evaluate", {"expression": "'a' + 'b'", "context": "repl"}
94+
)
95+
assert reply["success"]
96+
assert reply["body"]["result"] == ""
97+
98+
99+
def test_set_breakpoints(kernel_with_debug):
100+
code = """def f(a, b):
101+
c = a + b
102+
return c
103+
104+
f(2, 3)"""
105+
106+
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
107+
source = r["body"]["sourcePath"]
108+
109+
reply = wait_for_debug_request(
110+
kernel_with_debug,
111+
"setBreakpoints",
112+
{
113+
"breakpoints": [{"line": 2}],
114+
"source": {"path": source},
115+
"sourceModified": False,
116+
},
117+
)
118+
assert reply["success"]
119+
assert len(reply["body"]["breakpoints"]) == 1
120+
assert reply["body"]["breakpoints"][0]["verified"]
121+
assert reply["body"]["breakpoints"][0]["source"]["path"] == source
122+
123+
r = wait_for_debug_request(kernel_with_debug, "debugInfo")
124+
assert source in map(lambda b: b["source"], r["body"]["breakpoints"])
125+
126+
r = wait_for_debug_request(kernel_with_debug, "configurationDone")
127+
assert r["success"]
128+
129+
130+
def test_rich_inspect_not_at_breakpoint(kernel_with_debug):
131+
var_name = "text"
132+
value = "Hello the world"
133+
code = """{0}='{1}'
134+
print({0})
135+
""".format(var_name, value)
136+
137+
msg_id = kernel_with_debug.execute(code)
138+
get_reply(kernel_with_debug, msg_id)
139+
140+
r = wait_for_debug_request(kernel_with_debug, "inspectVariables")
141+
assert var_name in list(map(lambda v: v["name"], r["body"]["variables"]))
142+
143+
reply = wait_for_debug_request(
144+
kernel_with_debug,
145+
"richInspectVariables",
146+
{"variableName": var_name},
147+
)
148+
149+
assert reply["body"]["data"] == {"text/plain": "'{}'".format(value)}
150+
151+
152+
def test_rich_inspect_at_breakpoint(kernel_with_debug):
153+
code = """def f(a, b):
154+
c = a + b
155+
return c
156+
157+
f(2, 3)"""
158+
159+
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
160+
source = r["body"]["sourcePath"]
161+
162+
wait_for_debug_request(
163+
kernel_with_debug,
164+
"setBreakpoints",
165+
{
166+
"breakpoints": [{"line": 2}],
167+
"source": {"path": source},
168+
"sourceModified": False,
169+
},
170+
)
171+
172+
r = wait_for_debug_request(kernel_with_debug, "debugInfo")
173+
174+
r = wait_for_debug_request(kernel_with_debug, "configurationDone")
175+
176+
kernel_with_debug.execute(code)
177+
178+
stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})[
179+
"body"
180+
]["stackFrames"]
181+
182+
scopes = wait_for_debug_request(
183+
kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]}
184+
)["body"]["scopes"]
185+
186+
locals_ = wait_for_debug_request(
187+
kernel_with_debug,
188+
"variables",
189+
{
190+
"variablesReference": next(filter(lambda s: s["name"] == "Locals", scopes))[
191+
"variablesReference"
192+
]
193+
},
194+
)["body"]["variables"]
195+
196+
reply = wait_for_debug_request(
197+
kernel_with_debug,
198+
"richInspectVariables",
199+
{"variableName": locals_[0]["name"], "frameId": stacks[0]["id"]},
200+
)
201+
202+
assert reply["body"]["data"] == {"text/plain": locals_[0]["value"]}

0 commit comments

Comments
 (0)