Skip to content

Commit 66eabba

Browse files
authored
added context methods to the SDK + tests (#179)
* added context methods to the SDK + tests * lint * format * remove body return on delete context
1 parent 483830e commit 66eabba

File tree

8 files changed

+468
-12
lines changed

8 files changed

+468
-12
lines changed

.changeset/open-lamps-drop.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@e2b/code-interpreter-python': minor
3+
'@e2b/code-interpreter': minor
4+
---
5+
6+
added context methods to the sdk

js/src/sandbox.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,96 @@ export class Sandbox extends BaseSandbox {
320320
throw formatRequestTimeoutError(error)
321321
}
322322
}
323+
324+
/**
325+
* Removes a context.
326+
*
327+
* @param context context to remove.
328+
*
329+
* @returns void.
330+
*/
331+
async removeCodeContext(context: Context | string): Promise<void> {
332+
try {
333+
const id = typeof context === 'string' ? context : context.id
334+
const res = await fetch(`${this.jupyterUrl}/contexts/${id}`, {
335+
method: 'DELETE',
336+
headers: {
337+
'Content-Type': 'application/json',
338+
...this.connectionConfig.headers,
339+
},
340+
keepalive: true,
341+
signal: this.connectionConfig.getSignal(
342+
this.connectionConfig.requestTimeoutMs
343+
),
344+
})
345+
346+
const error = await extractError(res)
347+
if (error) {
348+
throw error
349+
}
350+
} catch (error) {
351+
throw formatRequestTimeoutError(error)
352+
}
353+
}
354+
355+
/**
356+
* List all contexts.
357+
*
358+
* @returns list of contexts.
359+
*/
360+
async listCodeContexts(): Promise<Context[]> {
361+
try {
362+
const res = await fetch(`${this.jupyterUrl}/contexts`, {
363+
method: 'GET',
364+
headers: {
365+
'Content-Type': 'application/json',
366+
...this.connectionConfig.headers,
367+
},
368+
keepalive: true,
369+
signal: this.connectionConfig.getSignal(
370+
this.connectionConfig.requestTimeoutMs
371+
),
372+
})
373+
374+
const error = await extractError(res)
375+
if (error) {
376+
throw error
377+
}
378+
379+
return await res.json()
380+
} catch (error) {
381+
throw formatRequestTimeoutError(error)
382+
}
383+
}
384+
385+
/**
386+
* Restart a context.
387+
*
388+
* @param context context to restart.
389+
*
390+
* @returns void.
391+
*/
392+
async restartCodeContext(context: Context | string): Promise<void> {
393+
try {
394+
const id = typeof context === 'string' ? context : context.id
395+
const res = await fetch(`${this.jupyterUrl}/contexts/${id}/restart`, {
396+
method: 'POST',
397+
headers: {
398+
'Content-Type': 'application/json',
399+
...this.connectionConfig.headers,
400+
},
401+
keepalive: true,
402+
signal: this.connectionConfig.getSignal(
403+
this.connectionConfig.requestTimeoutMs
404+
),
405+
})
406+
407+
const error = await extractError(res)
408+
if (error) {
409+
throw error
410+
}
411+
} catch (error) {
412+
throw formatRequestTimeoutError(error)
413+
}
414+
}
323415
}

js/tests/contexts.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect } from 'vitest'
2+
3+
import { sandboxTest } from './setup'
4+
5+
sandboxTest('create context with no options', async ({ sandbox }) => {
6+
const context = await sandbox.createCodeContext()
7+
8+
const contexts = await sandbox.listCodeContexts()
9+
const lastContext = contexts[contexts.length - 1]
10+
11+
expect(lastContext.id).toBe(context.id)
12+
expect(lastContext.language).toBe(context.language)
13+
expect(lastContext.cwd).toBe(context.cwd)
14+
})
15+
16+
sandboxTest('create context with options', async ({ sandbox }) => {
17+
const context = await sandbox.createCodeContext({
18+
language: 'python',
19+
cwd: '/root',
20+
})
21+
22+
const contexts = await sandbox.listCodeContexts()
23+
const lastContext = contexts[contexts.length - 1]
24+
25+
expect(lastContext.id).toBe(context.id)
26+
expect(lastContext.language).toBe(context.language)
27+
expect(lastContext.cwd).toBe(context.cwd)
28+
})
29+
30+
sandboxTest('remove context', async ({ sandbox }) => {
31+
const context = await sandbox.createCodeContext()
32+
33+
await sandbox.removeCodeContext(context.id)
34+
const contexts = await sandbox.listCodeContexts()
35+
36+
expect(contexts.map((context) => context.id)).not.toContain(context.id)
37+
})
38+
39+
sandboxTest('list contexts', async ({ sandbox }) => {
40+
const contexts = await sandbox.listCodeContexts()
41+
42+
// default contexts should include python and javascript
43+
expect(contexts.map((context) => context.language)).toContain('python')
44+
expect(contexts.map((context) => context.language)).toContain('javascript')
45+
})
46+
47+
sandboxTest('restart context', async ({ sandbox }) => {
48+
const context = await sandbox.createCodeContext()
49+
50+
// set a variable in the context
51+
await sandbox.runCode('x = 1', { context: context })
52+
53+
// restart the context
54+
await sandbox.restartCodeContext(context.id)
55+
56+
// check that the variable no longer exists
57+
const execution = await sandbox.runCode('x', { context: context })
58+
59+
// check for an NameError with message "name 'x' is not defined"
60+
expect(execution.error).toBeDefined()
61+
expect(execution.error?.name).toBe('NameError')
62+
expect(execution.error?.value).toBe("name 'x' is not defined")
63+
})

python/e2b_code_interpreter/code_interpreter_async.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import httpx
33

4-
from typing import Optional, Dict, overload, Union, Literal
4+
from typing import Optional, Dict, overload, Union, Literal, List
55
from httpx import AsyncClient
66

77
from e2b import (
@@ -273,3 +273,89 @@ async def create_code_context(
273273
return Context.from_json(data)
274274
except httpx.TimeoutException:
275275
raise format_request_timeout_error()
276+
277+
async def remove_code_context(
278+
self,
279+
context: Union[Context, str],
280+
) -> None:
281+
"""
282+
Removes a context.
283+
284+
:param context: Context to remove. Can be a Context object or a context ID string.
285+
286+
:return: None
287+
"""
288+
context_id = context.id if isinstance(context, Context) else context
289+
290+
headers: Dict[str, str] = {}
291+
if self._envd_access_token:
292+
headers = {"X-Access-Token": self._envd_access_token}
293+
294+
try:
295+
response = await self._client.delete(
296+
f"{self._jupyter_url}/contexts/{context_id}",
297+
headers=headers,
298+
timeout=self.connection_config.request_timeout,
299+
)
300+
301+
err = await aextract_exception(response)
302+
if err:
303+
raise err
304+
except httpx.TimeoutException:
305+
raise format_request_timeout_error()
306+
307+
async def list_code_contexts(self) -> List[Context]:
308+
"""
309+
List all contexts.
310+
311+
:return: List of contexts.
312+
"""
313+
headers: Dict[str, str] = {}
314+
if self._envd_access_token:
315+
headers = {"X-Access-Token": self._envd_access_token}
316+
317+
try:
318+
response = await self._client.get(
319+
f"{self._jupyter_url}/contexts",
320+
headers=headers,
321+
timeout=self.connection_config.request_timeout,
322+
)
323+
324+
err = await aextract_exception(response)
325+
if err:
326+
raise err
327+
328+
data = response.json()
329+
return [Context.from_json(context_data) for context_data in data]
330+
except httpx.TimeoutException:
331+
raise format_request_timeout_error()
332+
333+
async def restart_code_context(
334+
self,
335+
context: Union[Context, str],
336+
) -> None:
337+
"""
338+
Restart a context.
339+
340+
:param context: Context to restart. Can be a Context object or a context ID string.
341+
342+
:return: None
343+
"""
344+
context_id = context.id if isinstance(context, Context) else context
345+
346+
headers: Dict[str, str] = {}
347+
if self._envd_access_token:
348+
headers = {"X-Access-Token": self._envd_access_token}
349+
350+
try:
351+
response = await self._client.post(
352+
f"{self._jupyter_url}/contexts/{context_id}/restart",
353+
headers=headers,
354+
timeout=self.connection_config.request_timeout,
355+
)
356+
357+
err = await aextract_exception(response)
358+
if err:
359+
raise err
360+
except httpx.TimeoutException:
361+
raise format_request_timeout_error()

python/e2b_code_interpreter/code_interpreter_sync.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import httpx
33

4-
from typing import Optional, Dict, overload, Literal, Union
4+
from typing import Optional, Dict, overload, Literal, Union, List
55
from httpx import Client
66
from e2b import Sandbox as BaseSandbox, InvalidArgumentException
77

@@ -270,3 +270,89 @@ def create_code_context(
270270
return Context.from_json(data)
271271
except httpx.TimeoutException:
272272
raise format_request_timeout_error()
273+
274+
def remove_code_context(
275+
self,
276+
context: Union[Context, str],
277+
) -> None:
278+
"""
279+
Removes a context.
280+
281+
:param context: Context to remove. Can be a Context object or a context ID string.
282+
283+
:return: None
284+
"""
285+
context_id = context.id if isinstance(context, Context) else context
286+
287+
headers: Dict[str, str] = {}
288+
if self._envd_access_token:
289+
headers = {"X-Access-Token": self._envd_access_token}
290+
291+
try:
292+
response = self._client.delete(
293+
f"{self._jupyter_url}/contexts/{context_id}",
294+
headers=headers,
295+
timeout=self.connection_config.request_timeout,
296+
)
297+
298+
err = extract_exception(response)
299+
if err:
300+
raise err
301+
except httpx.TimeoutException:
302+
raise format_request_timeout_error()
303+
304+
def list_code_contexts(self) -> List[Context]:
305+
"""
306+
List all contexts.
307+
308+
:return: List of contexts.
309+
"""
310+
headers: Dict[str, str] = {}
311+
if self._envd_access_token:
312+
headers = {"X-Access-Token": self._envd_access_token}
313+
314+
try:
315+
response = self._client.get(
316+
f"{self._jupyter_url}/contexts",
317+
headers=headers,
318+
timeout=self.connection_config.request_timeout,
319+
)
320+
321+
err = extract_exception(response)
322+
if err:
323+
raise err
324+
325+
data = response.json()
326+
return [Context.from_json(context_data) for context_data in data]
327+
except httpx.TimeoutException:
328+
raise format_request_timeout_error()
329+
330+
def restart_code_context(
331+
self,
332+
context: Union[Context, str],
333+
) -> None:
334+
"""
335+
Restart a context.
336+
337+
:param context: Context to restart. Can be a Context object or a context ID string.
338+
339+
:return: None
340+
"""
341+
context_id = context.id if isinstance(context, Context) else context
342+
343+
headers: Dict[str, str] = {}
344+
if self._envd_access_token:
345+
headers = {"X-Access-Token": self._envd_access_token}
346+
347+
try:
348+
response = self._client.post(
349+
f"{self._jupyter_url}/contexts/{context_id}/restart",
350+
headers=headers,
351+
timeout=self.connection_config.request_timeout,
352+
)
353+
354+
err = extract_exception(response)
355+
if err:
356+
raise err
357+
except httpx.TimeoutException:
358+
raise format_request_timeout_error()

0 commit comments

Comments
 (0)