Skip to content

Commit 0031ebf

Browse files
committed
Add graphs as separate result attribute, improve schema, add typing, add supergraph
1 parent a1dcb01 commit 0031ebf

33 files changed

+1429
-328
lines changed

js/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const sandbox = await CodeInterpreter.create()
2424
await sandbox.notebook.execCell('x = 1')
2525

2626
const execution = await sandbox.notebook.execCell('x+=1; x')
27-
console.log(execution.text) // outputs 2
27+
console.log(execution.text) // outputs 2
2828

2929
await sandbox.close()
3030
```
@@ -48,7 +48,7 @@ plt.show()
4848
`
4949

5050
// you can install dependencies in "jupyter notebook style"
51-
await sandbox.notebook.execCell("!pip install matplotlib")
51+
await sandbox.notebook.execCell('!pip install matplotlib')
5252

5353
const execution = await sandbox.notebook.execCell(code)
5454

@@ -80,7 +80,7 @@ const sandbox = await CodeInterpreter.create()
8080
await sandbox.notebook.execCell(code, {
8181
onStdout: (out) => console.log(out),
8282
onStderr: (outErr) => console.error(outErr),
83-
onResult: (result) => console.log(result.text)
83+
onResult: (result) => console.log(result.text),
8484
})
8585

8686
await sandbox.close()

js/src/graphs.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
export enum GraphType {
2+
LINE = 'line',
3+
SCATTER = 'scatter',
4+
BAR = 'bar',
5+
PIE = 'pie',
6+
BOX_AND_WHISKER = 'box_and_whisker',
7+
SUPERGRAPH = 'supergraph',
8+
UNKNOWN = 'unknown',
9+
}
10+
11+
export type Graph = {
12+
type: GraphType
13+
title: string
14+
elements: any[]
15+
}
16+
17+
type Graph2D = Graph & {
18+
x_label?: string
19+
y_label?: string
20+
x_unit?: string
21+
y_unit?: string
22+
}
23+
24+
export type PointData = {
25+
label: string
26+
points: [number, number][]
27+
}
28+
29+
type PointGraph = Graph2D & {
30+
x_ticks: number[]
31+
x_tick_labels: string[]
32+
y_ticks: number[]
33+
y_tick_labels: string[]
34+
elements: PointData[]
35+
}
36+
37+
export type LineGraph = PointGraph & {
38+
type: GraphType.LINE
39+
}
40+
41+
export type ScatterGraph = PointGraph & {
42+
type: GraphType.SCATTER
43+
}
44+
45+
export type BarData = {
46+
label: string
47+
value: string
48+
}
49+
50+
export type BarGraph = Graph2D & {
51+
type: GraphType.BAR
52+
elements: BarData[]
53+
}
54+
55+
export type PieData = {
56+
label: string
57+
angle: number
58+
radius: number
59+
}
60+
61+
export type PieGraph = Graph & {
62+
type: GraphType.PIE
63+
elements: PieData[]
64+
}
65+
66+
export type BoxAndWhiskerData = {
67+
label: string
68+
min: number
69+
first_quartile: number
70+
median: number
71+
third_quartile: number
72+
max: number
73+
}
74+
75+
export type BoxAndWhiskerGraph = Graph2D & {
76+
type: GraphType.BOX_AND_WHISKER
77+
elements: BoxAndWhiskerData[]
78+
}
79+
80+
export type SuperGraph = Graph & {
81+
type: GraphType.SUPERGRAPH
82+
elements: Graph[]
83+
}
84+
85+
export type GraphTypes =
86+
| LineGraph
87+
| ScatterGraph
88+
| BarGraph
89+
| PieGraph
90+
| BoxAndWhiskerGraph
91+
| SuperGraph
92+
export function deserializeGraph(data: any): Graph {
93+
switch (data.type) {
94+
case GraphType.LINE:
95+
return { ...data } as LineGraph
96+
case GraphType.SCATTER:
97+
return { ...data } as ScatterGraph
98+
case GraphType.BAR:
99+
return { ...data } as BarGraph
100+
case GraphType.PIE:
101+
return { ...data } as PieGraph
102+
case GraphType.BOX_AND_WHISKER:
103+
return { ...data } as BoxAndWhiskerGraph
104+
case GraphType.SUPERGRAPH:
105+
const graphs = data.data.map((g: any) => deserializeGraph(g))
106+
delete data.data
107+
return {
108+
...data,
109+
data: graphs,
110+
} as SuperGraph
111+
default:
112+
return { ...data, type: GraphType.UNKNOWN } as Graph
113+
}
114+
}

js/src/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,30 @@ export * from 'e2b'
22

33
export { CodeInterpreter, JupyterExtension } from './codeInterpreter'
44

5-
export type { Logs, ExecutionError, Result, Execution, MIMEType, RawData, OutputMessage } from './messaging'
6-
5+
export type {
6+
Logs,
7+
ExecutionError,
8+
Result,
9+
Execution,
10+
MIMEType,
11+
RawData,
12+
OutputMessage,
13+
} from './messaging'
14+
export type {
15+
GraphType,
16+
GraphTypes,
17+
Graph,
18+
BarGraph,
19+
BarData,
20+
LineGraph,
21+
ScatterGraph,
22+
BoxAndWhiskerGraph,
23+
BoxAndWhiskerData,
24+
PieGraph,
25+
PieData,
26+
SuperGraph,
27+
PointData,
28+
} from './graphs'
729
import { CodeInterpreter } from './codeInterpreter'
830

931
export default CodeInterpreter

js/src/messaging.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NotFoundError, SandboxError, TimeoutError } from 'e2b'
2+
import { GraphTypes } from './graphs'
23

34
export async function extractError(res: Response) {
45
if (res.ok) {
@@ -24,9 +25,8 @@ export class OutputMessage {
2425
* Unix epoch in nanoseconds
2526
*/
2627
public readonly timestamp: number,
27-
public readonly error: boolean,
28-
) {
29-
}
28+
public readonly error: boolean
29+
) {}
3030

3131
public toString() {
3232
return this.line
@@ -50,25 +50,26 @@ export class ExecutionError {
5050
/**
5151
* The raw traceback of the error.
5252
**/
53-
public traceback: string,
54-
) { }
53+
public traceback: string
54+
) {}
5555
}
5656

5757
/**
5858
* Represents a MIME type.
5959
*/
6060
export type MIMEType = string
6161

62-
type Data = {
62+
type E2BData = {
6363
data: Record<string, unknown>
64+
graph: GraphTypes
6465
}
6566

6667
/**
6768
* Dictionary that maps MIME types to their corresponding representations of the data.
6869
*/
6970
export type RawData = {
7071
[key: MIMEType]: string
71-
} & Data
72+
} & E2BData
7273

7374
/**
7475
* Represents the data to be displayed as a result of executing a cell in a Jupyter notebook.
@@ -124,6 +125,10 @@ export class Result {
124125
* Contains the data from DataFrame.
125126
*/
126127
readonly data?: Record<string, unknown>
128+
/**
129+
* Contains the graph data.
130+
*/
131+
readonly graph?: GraphTypes
127132
/**
128133
* Extra data that can be included. Not part of the standard types.
129134
*/
@@ -146,10 +151,12 @@ export class Result {
146151
this.latex = data['latex']
147152
this.json = data['json']
148153
this.javascript = data['javascript']
149-
this.data = data['data']
150154
this.isMainResult = isMainResult
151155
this.raw = data
152156

157+
this.data = data['data']
158+
this.graph = data['graph']
159+
153160
this.extra = {}
154161

155162
for (const key of Object.keys(data)) {
@@ -166,7 +173,7 @@ export class Result {
166173
'json',
167174
'javascript',
168175
'data',
169-
'extra'
176+
'extra',
170177
].includes(key)
171178
) {
172179
this.extra[key] = data[key]
@@ -234,7 +241,7 @@ export class Result {
234241
latex: this.latex,
235242
json: this.json,
236243
javascript: this.javascript,
237-
...(Object.keys(this.extra).length > 0 ? { extra: this.extra } : {})
244+
...(Object.keys(this.extra).length > 0 ? { extra: this.extra } : {}),
238245
}
239246
}
240247
}
@@ -274,7 +281,7 @@ export class Execution {
274281
* Execution count of the cell.
275282
*/
276283
public executionCount?: number
277-
) { }
284+
) {}
278285

279286
/**
280287
* Returns the text representation of the main result of the cell.
@@ -294,23 +301,26 @@ export class Execution {
294301
return {
295302
results: this.results,
296303
logs: this.logs,
297-
error: this.error
304+
error: this.error,
298305
}
299306
}
300307
}
301308

302309
export async function parseOutput(
303310
execution: Execution,
304311
line: string,
305-
onStdout?: (output: OutputMessage) => (Promise<any> | any),
306-
onStderr?: (output: OutputMessage) => (Promise<any> | any),
307-
onResult?: (data: Result) => (Promise<any> | any),
312+
onStdout?: (output: OutputMessage) => Promise<any> | any,
313+
onStderr?: (output: OutputMessage) => Promise<any> | any,
314+
onResult?: (data: Result) => Promise<any> | any
308315
) {
309316
const msg = JSON.parse(line)
310317

311318
switch (msg.type) {
312319
case 'result':
313-
const result = new Result({ ...msg, type: undefined, is_main_result: undefined }, msg.is_main_result)
320+
const result = new Result(
321+
{ ...msg, type: undefined, is_main_result: undefined },
322+
msg.is_main_result
323+
)
314324
execution.results.push(result)
315325
if (onResult) {
316326
await onResult(result)

js/tests/displayData.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { expect } from 'vitest'
33
import { sandboxTest } from './setup'
44

55
sandboxTest('display data', async ({ sandbox }) => {
6-
76
// plot random graph
87
const result = await sandbox.notebook.execCell(`
98
import matplotlib.pyplot as plt

js/tests/envVars.test.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,46 @@
11
import { expect } from 'vitest'
22

33
import { sandboxTest } from './setup'
4-
import { CodeInterpreter } from "../src";
4+
import { CodeInterpreter } from '../src'
55

66
// Skip this test if we are running in debug mode — the pwd and user in the testing docker container are not the same as in the actual sandbox.
77
sandboxTest('env vars', async () => {
8-
const sandbox = await CodeInterpreter.create({ envs: { TEST_ENV_VAR: "supertest" } })
9-
const result = await sandbox.notebook.execCell(`import os; x = os.getenv('TEST_ENV_VAR'); x`)
8+
const sandbox = await CodeInterpreter.create({
9+
envs: { TEST_ENV_VAR: 'supertest' },
10+
})
11+
const result = await sandbox.notebook.execCell(
12+
`import os; x = os.getenv('TEST_ENV_VAR'); x`
13+
)
1014

1115
expect(result.results[0].text.trim()).toEqual('supertest')
1216
})
1317

14-
sandboxTest('env vars on sandbox', async ({sandbox}) => {
15-
const result = await sandbox.notebook.execCell("import os; os.getenv('FOO')", {envs: {FOO: "bar"}})
18+
sandboxTest('env vars on sandbox', async ({ sandbox }) => {
19+
const result = await sandbox.notebook.execCell(
20+
"import os; os.getenv('FOO')",
21+
{ envs: { FOO: 'bar' } }
22+
)
1623

1724
expect(result.results[0].text.trim()).toEqual('bar')
1825
})
1926

2027
sandboxTest('env vars on sandbox override', async () => {
21-
const sandbox = await CodeInterpreter.create({ envs: { FOO: "bar", SBX: "value" } })
22-
await sandbox.notebook.execCell("import os; os.environ['FOO'] = 'bar'; os.environ['RUNTIME_ENV'] = 'value'")
23-
const result = await sandbox.notebook.execCell("import os; os.getenv('FOO')", {envs: {FOO: "baz"}})
28+
const sandbox = await CodeInterpreter.create({
29+
envs: { FOO: 'bar', SBX: 'value' },
30+
})
31+
await sandbox.notebook.execCell(
32+
"import os; os.environ['FOO'] = 'bar'; os.environ['RUNTIME_ENV'] = 'value'"
33+
)
34+
const result = await sandbox.notebook.execCell(
35+
"import os; os.getenv('FOO')",
36+
{ envs: { FOO: 'baz' } }
37+
)
2438

2539
expect(result.results[0].text.trim()).toEqual('baz')
2640

27-
const result2 = await sandbox.notebook.execCell("import os; os.getenv('RUNTIME_ENV')")
41+
const result2 = await sandbox.notebook.execCell(
42+
"import os; os.getenv('RUNTIME_ENV')"
43+
)
2844
expect(result2.results[0].text.trim()).toEqual('value')
2945

3046
const result3 = await sandbox.notebook.execCell("import os; os.getenv('SBX')")

0 commit comments

Comments
 (0)