Skip to content

Commit e01c09e

Browse files
authored
Merge pull request #122 from fehrlich/master
python support
2 parents 6543180 + 86e71ae commit e01c09e

File tree

10 files changed

+347
-7
lines changed

10 files changed

+347
-7
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,9 @@ typings/
8080
.dynamodb/
8181

8282
dist/
83-
out/
83+
out/
84+
85+
# Python byte-compiled / optimized / DLL files
86+
__pycache__/
87+
*.py[cod]
88+
*$py.class

data-extraction/src/CommonDataTypes.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ export type PlotlyVisualizationData = {
228228
text?: string | string[];
229229
xaxis?: string;
230230
yaxis?: string;
231+
cells?: {
232+
values?: string[][];
233+
};
234+
header?: {
235+
values?: string[];
236+
};
237+
domain?: {
238+
x?: number[],
239+
y?: number[],
240+
};
231241
x?: (string | number | null)[] | (string | number | null)[][];
232242
y?: (string | number | null)[] | (string | number | null)[][];
233243
z?: (string | number | null)[] | (string | number | null)[][];
@@ -257,7 +267,8 @@ export type PlotlyVisualizationData = {
257267
| "waterfall"
258268
| "funnel"
259269
| "funnelarea"
260-
| "scattermapbox";
270+
| "scattermapbox"
271+
| "table";
261272
mode?:
262273
| "lines"
263274
| "markers"

demos/python/Person.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Person:
2+
def __init__(self, name, parents=None) -> None:
3+
self.name = name
4+
self.parents = [] if parents is None else parents
5+
6+
def addParent(self, parent: "Person"):
7+
self.parents.append(parent)

demos/python/debugvisualizer.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from Person import Person
2+
import json
3+
from vscodedebugvisualizer import globalVisualizationFactory
4+
5+
6+
class PersonVisualizer:
7+
def checkType(self, t):
8+
return isinstance(t, Person)
9+
10+
def visualizePerson(self, person: Person, nodes=[], edges=[]):
11+
if person.name in [n["id"] for n in nodes]:
12+
return nodes, edges
13+
14+
nodes.append(
15+
{
16+
"id": person.name,
17+
"label": person.name,
18+
}
19+
)
20+
21+
for p in person.parents:
22+
nodes, edges = self.visualizePerson(p, nodes, edges)
23+
edges.append(
24+
{
25+
"from": p.name,
26+
"to": person.name,
27+
}
28+
)
29+
30+
return nodes, edges
31+
32+
def visualize(self, person: Person):
33+
jsonDict = {
34+
"kind": {"graph": True},
35+
"nodes": [],
36+
"edges": [],
37+
}
38+
39+
self.visualizePerson(person, jsonDict["nodes"], jsonDict["edges"])
40+
41+
return json.dumps(jsonDict)
42+
43+
44+
globalVisualizationFactory.addVisualizer(PersonVisualizer())

demos/python/demo.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import numpy as np
2+
3+
4+
# put "x" in the Debug Visualizer and use step by step
5+
# debugging to see the diffrent types of data visualization
6+
7+
8+
x = 5
9+
x = "asdf"
10+
x = 5.5
11+
x = [1, 2, 3, 4, 5, 6, "a", "b"]
12+
x = ["b", "a", "c", "d", "e"]
13+
x.sort()
14+
x = {
15+
"asdf1": "dasdf",
16+
"asdf2": "dasdf",
17+
"asdf3": {"foo": "bar"},
18+
}
19+
20+
x = [1, 2, 3, 4, 5, 6]
21+
# histogram
22+
x = np.concatenate([np.arange(i) for i in range(9)])
23+
x = x.reshape(2, -1)
24+
25+
26+
# one dimension
27+
x = np.arange(100)
28+
x = np.linspace(-np.pi, np.pi, 100)
29+
x = np.sin(x)
30+
31+
# 2 dimension
32+
x = x.reshape(5, 20)
33+
# 2 dimension transpose
34+
x = x.transpose()
35+
x = x.transpose()
36+
37+
# 3 dimensions
38+
x = x.reshape(2, 5, 10)
39+
40+
# 4 dimensions
41+
x = x.reshape(2, 5, 2, 5)
42+
43+
# big number
44+
x = np.empty(2 ** 24)
45+
x[0:1000000] = 1
46+
x[1000000:10000000] = 5
47+
48+
# pyTorch
49+
try:
50+
import torch
51+
52+
x = np.concatenate([np.arange(i) for i in range(9)])
53+
x = x.reshape(2, -1)
54+
x = torch.Tensor(x)
55+
pass
56+
57+
except ImportError:
58+
pass
59+
60+
61+
# tensorflow
62+
63+
try:
64+
import tensorflow as tf
65+
66+
x = np.concatenate([np.arange(i) for i in range(9)])
67+
x = x.reshape(2, -1)
68+
x = tf.convert_to_tensor(x)
69+
pass
70+
except ImportError:
71+
pass
72+
73+
74+
# pandas
75+
76+
try:
77+
import pandas as pd
78+
import plotly.express as px
79+
80+
x = px.data.gapminder().query("year == 2007")
81+
pass
82+
except ImportError:
83+
pass
84+
85+
# custom visualizer defined in ./debugvisualizer.py (this file is included automatically)
86+
87+
from Person import Person
88+
89+
x = Person("Aria")
90+
parent1 = Person("Eduart")
91+
parent2 = Person("Catelyn")
92+
x.addParent(parent1)
93+
x.addParent(parent2)
94+
parent1.addParent(Person("Benjen"))
95+
96+
# direct debug-visualizer json as dict with property "kind"
97+
98+
x = {
99+
"kind": {"dotGraph": True},
100+
"text": '\ndigraph G {\n subgraph cluster_0 {\n style=filled;\n color=lightgrey;\n node [style=filled,color=white];\n a0 -> a1 -> a2 -> a3;\n label = "process #1";\n }\n \n subgraph cluster_1 {\n node [style=filled];\n b0 -> b1 -> b2 -> b3;\n label = "process #2";\n color=blue\n }\n start -> a0;\n start -> b0;\n a1 -> b3;\n b2 -> a3;\n a3 -> a0;\n a3 -> end;\n b3 -> end;\n \n start [shape=Mdiamond];\n end [shape=Msquare];\n}\n',
101+
}
102+
103+
x = {
104+
"kind": {"plotly": True},
105+
"data": [
106+
{
107+
"mode": "lines",
108+
"type": "scatter",
109+
"x": ["A", "B", "C"],
110+
"xaxis": "x",
111+
"y": [4488916, 3918072, 3892124],
112+
"yaxis": "y",
113+
},
114+
{
115+
"cells": {"values": [["A", "B", "C"], [341319, 281489, 294786], [4488916, 3918072, 3892124]]},
116+
"domain": {"x": [0.0, 1.0], "y": [0.0, 0.60]},
117+
"header": {"align": "left", "font": {"size": 10}, "values": ["Date", "Number", "Output"]},
118+
"type": "table",
119+
},
120+
],
121+
"layout": {"xaxis": {"anchor": "y", "domain": [0.0, 1.0]}, "yaxis": {"anchor": "x", "domain": [0.75, 1.0]}},
122+
}
123+
pass

demos/python/graph.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@
2323
# connects the node to a random edge
2424
targetId = str(randint(1, i - 1))
2525
graph["edges"].append({"from": id, "to": targetId})
26-
json_graph = dumps(graph)
2726
print("i is " + str(i))
2827
# try setting a breakpoint right above
29-
# then put json_graph into the visualization console and press enter
28+
# then put graph into the visualization console and press enter
3029
# when you step through the code each time you hit the breakpoint
3130
# the graph should automatically refresh!
3231

demos/python/insertion_sort.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Python demo for sorting using VS Code Debug Visualizer."""
2-
import json
32

43

54
def serialize(arr):
@@ -14,7 +13,7 @@ def serialize(arr):
1413
}
1514
],
1615
}
17-
return json.dumps(formatted)
16+
return formatted
1817

1918

2019
arr = [6, 9, 3, 12, 1, 11, 5, 13, 8, 14, 2, 4, 10, 0, 7]
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
DataExtractionResult,
3+
DataExtractorId,
4+
GraphNode,
5+
GraphVisualizationData,
6+
} from "@hediet/debug-visualizer-data-extraction";
7+
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
8+
import {
9+
DebugSessionVisualizationSupport,
10+
VisualizationBackend,
11+
GetVisualizationDataArgs,
12+
VisualizationBackendBase,
13+
} from "./VisualizationBackend";
14+
import { Config } from "../Config";
15+
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
16+
import { FormattedMessage } from "../webviewContract";
17+
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
18+
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
19+
20+
registerUpdateReconciler(module);
21+
22+
@hotClass(module)
23+
export class PyEvaluationEngine
24+
implements DebugSessionVisualizationSupport {
25+
constructor(
26+
private readonly debuggerView: DebuggerViewProxy,
27+
private readonly config: Config
28+
) { }
29+
30+
createBackend(
31+
session: DebugSessionProxy
32+
): VisualizationBackend | undefined {
33+
34+
const supportedDebugAdapters = [
35+
"python",
36+
];
37+
38+
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
39+
return new PyVisualizationBackend(
40+
session,
41+
this.debuggerView,
42+
this.config
43+
);
44+
}
45+
return undefined;
46+
}
47+
}
48+
49+
export class PyVisualizationBackend extends VisualizationBackendBase {
50+
public readonly expressionLanguageId = "python";
51+
52+
constructor(
53+
debugSession: DebugSessionProxy,
54+
debuggerView: DebuggerViewProxy,
55+
private readonly config: Config
56+
) {
57+
super(debugSession, debuggerView);
58+
}
59+
60+
protected getContext(): "watch" | "repl" {
61+
// we will use "repl" as default so that results are not truncated.
62+
return "repl";
63+
}
64+
65+
public async getVisualizationData({
66+
expression,
67+
preferredExtractorId,
68+
}: GetVisualizationDataArgs): Promise<
69+
| { kind: "data"; result: DataExtractionResult }
70+
| { kind: "error"; message: FormattedMessage }
71+
> {
72+
const frameId = this.debuggerView.getActiveStackFrameId(
73+
this.debugSession
74+
);
75+
76+
const finalExpression = this.getFinalExpression({
77+
expression,
78+
preferredExtractorId,
79+
});
80+
let reply: { result: string; variablesReference: number };
81+
try {
82+
// inject vscodedebugvisualizer for python
83+
await this.debugSession.evaluate({
84+
expression: 'from vscodedebugvisualizer import visualize\ntry:\n import debugvisualizer\nexcept ImportError:\n pass',
85+
frameId,
86+
context: this.getContext(),
87+
});
88+
89+
reply = await this.debugSession.evaluate({
90+
expression: finalExpression,
91+
frameId,
92+
context: this.getContext(),
93+
});
94+
95+
let result = reply.result;
96+
// remove the initial escape by the the debug session e.g. `''{"kind": {"text": true}, "text": "{"asdf1\'"}''`
97+
result = result.replace(/\\'/g, "'");
98+
result = result.replace(/\\\\/g, "\\");
99+
100+
return parseEvaluationResultFromGenericDebugAdapter(
101+
result,
102+
{
103+
debugAdapterType: this.debugSession.session
104+
.configuration.type,
105+
}
106+
);
107+
} catch (error) {
108+
let errorTyped = error as Error;
109+
if (errorTyped.message.includes("ModuleNotFoundError: No module named 'vscodedebugvisualizer'")) {
110+
return {
111+
kind: "error",
112+
message: {
113+
kind: "list",
114+
items: ["Pleas make sure vscodedebugvisualizer is installed: `pip install vscodedebugvisualizer`"],
115+
},
116+
};
117+
}
118+
return {
119+
kind: "error",
120+
message: {
121+
kind: "list",
122+
items: [
123+
"An error occurred while evaluating the expression:",
124+
errorTyped.message,
125+
`Used debug adapter: ${this.debugSession.session.configuration.type}`,
126+
{
127+
kind: "inlineList",
128+
items: [
129+
"Evaluated expression is",
130+
{ kind: "code", content: finalExpression },
131+
],
132+
},
133+
],
134+
},
135+
};
136+
}
137+
}
138+
139+
protected getFinalExpression(args: {
140+
expression: string;
141+
preferredExtractorId: DataExtractorId | undefined;
142+
}): string {
143+
// wrap expression with visualize function
144+
let pythonInject = "";
145+
pythonInject += "visualize(" + args.expression + ")";
146+
return pythonInject;
147+
}
148+
149+
}

0 commit comments

Comments
 (0)