diff --git a/.changeset/new-kings-sleep.md b/.changeset/new-kings-sleep.md new file mode 100644 index 00000000..d5cbd065 --- /dev/null +++ b/.changeset/new-kings-sleep.md @@ -0,0 +1,6 @@ +--- +'@e2b/code-interpreter-python': major +'@e2b/code-interpreter': major +--- + +SDK v2 diff --git a/.changeset/strange-donkeys-clean.md b/.changeset/strange-donkeys-clean.md new file mode 100644 index 00000000..ba41e0e8 --- /dev/null +++ b/.changeset/strange-donkeys-clean.md @@ -0,0 +1,5 @@ +--- +'@e2b/code-interpreter-template': minor +--- + +Propagate access token diff --git a/README.md b/README.md index e2d42665..812c3a02 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Python ```py from e2b_code_interpreter import Sandbox -with Sandbox() as sandbox: +with Sandbox.create() as sandbox: sandbox.run_code("x = 1") execution = sandbox.run_code("x+=1; x") print(execution.text) # outputs 2 diff --git a/js/package.json b/js/package.json index 938d5c2e..fa8d1b58 100644 --- a/js/package.json +++ b/js/package.json @@ -66,7 +66,7 @@ "vm" ], "dependencies": { - "e2b": "^1.4.0" + "e2b": "^2.0.1" }, "engines": { "node": ">=18" diff --git a/js/src/sandbox.ts b/js/src/sandbox.ts index 74db4c9e..89ffdd27 100644 --- a/js/src/sandbox.ts +++ b/js/src/sandbox.ts @@ -1,7 +1,18 @@ import { Sandbox as BaseSandbox, InvalidArgumentError } from 'e2b' -import { Result, Execution, OutputMessage, parseOutput, extractError, ExecutionError } from './messaging' -import { formatExecutionTimeoutError, formatRequestTimeoutError, readLines } from "./utils"; +import { + Result, + Execution, + OutputMessage, + parseOutput, + extractError, + ExecutionError, +} from './messaging' +import { + formatExecutionTimeoutError, + formatRequestTimeoutError, + readLines, +} from './utils' import { JUPYTER_PORT, DEFAULT_TIMEOUT_MS } from './consts' /** @@ -29,37 +40,37 @@ export interface RunCodeOpts { /** * Callback for handling stdout messages. */ - onStdout?: (output: OutputMessage) => (Promise | any), + onStdout?: (output: OutputMessage) => Promise | any /** * Callback for handling stderr messages. */ - onStderr?: (output: OutputMessage) => (Promise | any), + onStderr?: (output: OutputMessage) => Promise | any /** * Callback for handling the final execution result. */ - onResult?: (data: Result) => (Promise | any), + onResult?: (data: Result) => Promise | any /** * Callback for handling the `ExecutionError` object. */ - onError?: (error: ExecutionError) => (Promise | any), + onError?: (error: ExecutionError) => Promise | any /** * Custom environment variables for code execution. - * + * * @default {} */ - envs?: Record, + envs?: Record /** * Timeout for the code execution in **milliseconds**. - * + * * @default 60_000 // 60 seconds */ - timeoutMs?: number, + timeoutMs?: number /** * Timeout for the request in **milliseconds**. - * + * * @default 30_000 // 30 seconds */ - requestTimeoutMs?: number, + requestTimeoutMs?: number } /** @@ -68,22 +79,22 @@ export interface RunCodeOpts { export interface CreateCodeContextOpts { /** * Working directory for the context. - * + * * @default /home/user */ - cwd?: string, + cwd?: string /** * Language for the context. - * + * * @default python */ - language?: string, + language?: string /** * Timeout for the request in **milliseconds**. - * + * * @default 30_000 // 30 seconds */ - requestTimeoutMs?: number, + requestTimeoutMs?: number } /** @@ -108,18 +119,19 @@ export interface CreateCodeContextOpts { * ``` */ export class Sandbox extends BaseSandbox { - protected static override readonly defaultTemplate: string = 'code-interpreter-v1' + protected static override readonly defaultTemplate: string = + 'code-interpreter-v1' /** * Run the code as Python. - * + * * Specify the `language` or `context` option to run the code as a different language or in a different `Context`. - * + * * You can reference previously defined variables, imports, and functions in the code. * * @param code code to execute. * @param opts options for executing the code. - * + * * @returns `Execution` result object. */ async runCode( @@ -127,23 +139,23 @@ export class Sandbox extends BaseSandbox { opts?: RunCodeOpts & { /** * Language to use for code execution. - * + * * If not defined, the default Python context is used. */ - language?: 'python', - }, + language?: 'python' + } ): Promise /** * Run the code for the specified language. - * + * * Specify the `language` or `context` option to run the code as a different language or in a different `Context`. * If no language is specified, Python is used. - * + * * You can reference previously defined variables, imports, and functions in the code. * * @param code code to execute. * @param opts options for executing the code. - * + * * @returns `Execution` result object. */ async runCode( @@ -151,22 +163,22 @@ export class Sandbox extends BaseSandbox { opts?: RunCodeOpts & { /** * Language to use for code execution. - * + * * If not defined, the default Python context is used. */ - language?: string, - }, + language?: string + } ): Promise /** * Runs the code in the specified context, if not specified, the default context is used. - * + * * Specify the `language` or `context` option to run the code as a different language or in a different `Context`. - * + * * You can reference previously defined variables, imports, and functions in the code. * * @param code code to execute. * @param opts options for executing the code - * + * * @returns `Execution` result object */ async runCode( @@ -175,35 +187,44 @@ export class Sandbox extends BaseSandbox { /** * Context to run the code in. */ - context?: Context, - }, + context?: Context + } ): Promise async runCode( code: string, opts?: RunCodeOpts & { - language?: string, - context?: Context, - }, + language?: string + context?: Context + } ): Promise { if (opts?.context && opts?.language) { - throw new InvalidArgumentError("You can provide context or language, but not both at the same time.") + throw new InvalidArgumentError( + 'You can provide context or language, but not both at the same time.' + ) } const controller = new AbortController() - const requestTimeout = opts?.requestTimeoutMs ?? this.connectionConfig.requestTimeoutMs + const requestTimeout = + opts?.requestTimeoutMs ?? this.connectionConfig.requestTimeoutMs - const reqTimer = requestTimeout ? setTimeout(() => { - controller.abort() - }, requestTimeout) + const reqTimer = requestTimeout + ? setTimeout(() => { + controller.abort() + }, requestTimeout) : undefined + const headers: Record = { + 'Content-Type': 'application/json', + } + if (this.envdAccessToken) { + headers['X-Access-Token'] = this.envdAccessToken + } + try { const res = await fetch(`${this.jupyterUrl}/execute`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers, body: JSON.stringify({ code, context_id: opts?.context?.id, @@ -220,7 +241,9 @@ export class Sandbox extends BaseSandbox { } if (!res.body) { - throw new Error(`Not response body: ${res.statusText} ${await res?.text()}`) + throw new Error( + `Not response body: ${res.statusText} ${await res?.text()}` + ) } clearTimeout(reqTimer) @@ -229,16 +252,22 @@ export class Sandbox extends BaseSandbox { const bodyTimer = bodyTimeout ? setTimeout(() => { - controller.abort() - }, bodyTimeout) + controller.abort() + }, bodyTimeout) : undefined const execution = new Execution() - try { for await (const chunk of readLines(res.body)) { - await parseOutput(execution, chunk, opts?.onStdout, opts?.onStderr, opts?.onResult, opts?.onError) + await parseOutput( + execution, + chunk, + opts?.onStdout, + opts?.onStderr, + opts?.onResult, + opts?.onError + ) } } catch (error) { throw formatExecutionTimeoutError(error) @@ -256,7 +285,7 @@ export class Sandbox extends BaseSandbox { * Creates a new context to run code in. * * @param opts options for creating the context. - * + * * @returns context object. */ async createCodeContext(opts?: CreateCodeContextOpts): Promise { @@ -265,6 +294,7 @@ export class Sandbox extends BaseSandbox { method: 'POST', headers: { 'Content-Type': 'application/json', + ...this.connectionConfig.headers, }, body: JSON.stringify({ language: opts?.language, @@ -286,6 +316,8 @@ export class Sandbox extends BaseSandbox { } protected get jupyterUrl(): string { - return `${this.connectionConfig.debug ? 'http' : 'https'}://${this.getHost(JUPYTER_PORT)}` + return `${this.connectionConfig.debug ? 'http' : 'https'}://${this.getHost( + JUPYTER_PORT + )}` } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88e9a825..5123dd42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,8 +23,8 @@ importers: js: dependencies: e2b: - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^2.0.1 + version: 2.0.1 devDependencies: '@types/node': specifier: ^18.18.6 @@ -64,8 +64,8 @@ packages: resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} engines: {node: '>=6.9.0'} - '@bufbuild/protobuf@2.2.2': - resolution: {integrity: sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==} + '@bufbuild/protobuf@2.6.3': + resolution: {integrity: sha512-w/gJKME9mYN7ZoUAmSMAWXk4hkVpxRKvEJCb3dV5g9wwWdxTJJ0ayOJAVcNxtdqaxDyFuC0uz4RSGVacJ030PQ==} '@changesets/apply-release-plan@7.0.8': resolution: {integrity: sha512-qjMUj4DYQ1Z6qHawsn7S71SujrExJ+nceyKKyI9iB+M5p9lCL55afuEd6uLBPRpLGWQwkwvWegDHtwHJb1UjpA==} @@ -679,8 +679,8 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} - e2b@1.4.0: - resolution: {integrity: sha512-KGe5F5UI+1PZ82OBjPHsYqpbw33ck7j0xgcJRSS56mAOWMX/Z6xllXqbZj66Xg6kkO32GmSGXjCAZL4FMSfyug==} + e2b@2.0.1: + resolution: {integrity: sha512-wJgZTV1QFeh5WKQ23n6hWmMODPmyKiiWaQy+uxvV/5M9NH/zMY/ONzhh7j7ONYgeH8jdRZxOS2e+G345EodGeA==} engines: {node: '>=18'} eastasianwidth@0.2.0: @@ -1502,7 +1502,7 @@ snapshots: '@babel/runtime@7.27.1': {} - '@bufbuild/protobuf@2.2.2': {} + '@bufbuild/protobuf@2.6.3': {} '@changesets/apply-release-plan@7.0.8': dependencies: @@ -1646,14 +1646,14 @@ snapshots: human-id: 1.0.2 prettier: 2.8.8 - '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.2.2))': + '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.6.3)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.3))': dependencies: - '@bufbuild/protobuf': 2.2.2 - '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.2.2) + '@bufbuild/protobuf': 2.6.3 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.6.3) - '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.2.2)': + '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.3)': dependencies: - '@bufbuild/protobuf': 2.2.2 + '@bufbuild/protobuf': 2.6.3 '@esbuild/aix-ppc64@0.25.0': optional: true @@ -2089,11 +2089,11 @@ snapshots: dotenv@16.4.7: {} - e2b@1.4.0: + e2b@2.0.1: dependencies: - '@bufbuild/protobuf': 2.2.2 - '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.2.2) - '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.2.2)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.2.2)) + '@bufbuild/protobuf': 2.6.3 + '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.6.3) + '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.6.3)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.6.3)) compare-versions: 6.1.0 openapi-fetch: 0.9.8 platform: 1.3.6 diff --git a/python/README.md b/python/README.md index 4432c5d5..aec65804 100644 --- a/python/README.md +++ b/python/README.md @@ -36,7 +36,7 @@ E2B_API_KEY=e2b_*** ```py from e2b_code_interpreter import Sandbox -with Sandbox() as sandbox: +with Sandbox.create() as sandbox: sandbox.run_code("x = 1") execution = sandbox.run_code("x+=1; x") print(execution.text) # outputs 2 diff --git a/python/e2b_code_interpreter/code_interpreter_async.py b/python/e2b_code_interpreter/code_interpreter_async.py index c521af83..7821eaef 100644 --- a/python/e2b_code_interpreter/code_interpreter_async.py +++ b/python/e2b_code_interpreter/code_interpreter_async.py @@ -6,7 +6,6 @@ from e2b import ( AsyncSandbox as BaseAsyncSandbox, - ConnectionConfig, InvalidArgumentException, ) @@ -189,7 +188,7 @@ async def run_code( ) timeout = None if timeout == 0 else (timeout or DEFAULT_TIMEOUT) - request_timeout = request_timeout or self._connection_config.request_timeout + request_timeout = request_timeout or self.connection_config.request_timeout context_id = context.id if context else None try: @@ -202,6 +201,7 @@ async def run_code( "language": language, "env_vars": envs, }, + headers={"X-Access-Token": self._envd_access_token}, timeout=(request_timeout, timeout, request_timeout, request_timeout), ) as response: @@ -253,8 +253,9 @@ async def create_code_context( try: response = await self._client.post( f"{self._jupyter_url}/contexts", + headers={"X-Access-Token": self._envd_access_token}, json=data, - timeout=request_timeout or self._connection_config.request_timeout, + timeout=request_timeout or self.connection_config.request_timeout, ) err = await aextract_exception(response) diff --git a/python/e2b_code_interpreter/code_interpreter_sync.py b/python/e2b_code_interpreter/code_interpreter_sync.py index 22c47c0d..978c6dc5 100644 --- a/python/e2b_code_interpreter/code_interpreter_sync.py +++ b/python/e2b_code_interpreter/code_interpreter_sync.py @@ -41,13 +41,13 @@ class Sandbox(BaseSandbox): Check docs [here](https://e2b.dev/docs). - Use the `Sandbox()` to create a new sandbox. + Use the `Sandbox.create()` to create a new sandbox. Example: ```python from e2b_code_interpreter import Sandbox - sandbox = Sandbox() + sandbox = Sandbox.create() ``` """ @@ -185,7 +185,7 @@ def run_code( ) timeout = None if timeout == 0 else (timeout or DEFAULT_TIMEOUT) - request_timeout = request_timeout or self._connection_config.request_timeout + request_timeout = request_timeout or self.connection_config.request_timeout context_id = context.id if context else None try: @@ -198,6 +198,7 @@ def run_code( "language": language, "env_vars": envs, }, + headers={"X-Access-Token": self._envd_access_token}, timeout=(request_timeout, timeout, request_timeout, request_timeout), ) as response: err = extract_exception(response) @@ -249,7 +250,8 @@ def create_code_context( response = self._client.post( f"{self._jupyter_url}/contexts", json=data, - timeout=request_timeout or self._connection_config.request_timeout, + headers={"X-Access-Token": self._envd_access_token}, + timeout=request_timeout or self.connection_config.request_timeout, ) err = extract_exception(response) diff --git a/python/example.py b/python/example.py index 1557f794..56b447bf 100644 --- a/python/example.py +++ b/python/example.py @@ -34,7 +34,7 @@ async def run(): - sbx = Sandbox(timeout=60) + sbx = Sandbox.create(timeout=60) e = sbx.run_code(code) print(e.results[0].chart) diff --git a/python/poetry.lock b/python/poetry.lock index da406771..93bf6fe1 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -363,14 +363,14 @@ test = ["black", "pytest"] [[package]] name = "e2b" -version = "1.5.5" +version = "2.0.0" description = "E2B SDK that give agents cloud environments" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "e2b-1.5.5-py3-none-any.whl", hash = "sha256:cd6343193f5b941af33504d422cb39c02515a23a5c94c9ef9fdebeb140fb382e"}, - {file = "e2b-1.5.5.tar.gz", hash = "sha256:541dd0bd8b3ff8aa1f56a2719333d5b8e0465c30df7ebd93e2776cc15672503a"}, + {file = "e2b-2.0.0-py3-none-any.whl", hash = "sha256:a6621b905cb2a883a9c520736ae98343a6184fc90c29b4f2f079d720294a0df0"}, + {file = "e2b-2.0.0.tar.gz", hash = "sha256:4d033d937b0a09b8428e73233321a913cbaef8e7299fc731579c656e9d53a144"}, ] [package.dependencies] @@ -378,7 +378,7 @@ attrs = ">=23.2.0" httpcore = ">=1.0.5,<2.0.0" httpx = ">=0.27.0,<1.0.0" packaging = ">=24.1" -protobuf = ">=5.29.4,<6.0.0" +protobuf = ">=4.21.0" python-dateutil = ">=2.8.2" typing-extensions = ">=4.1.0" @@ -1193,4 +1193,4 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "166eb5f8bc1132d916fa088a8b99eb4611498215fcf720e5abe4681b2ab71958" +content-hash = "0e418edc53bd5281cad796e539debdbfbd6634a228b94336211d7198ce98d92b" diff --git a/python/pyproject.toml b/python/pyproject.toml index 4344de46..1630b686 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -14,7 +14,7 @@ python = "^3.9" httpx = ">=0.20.0, <1.0.0" attrs = ">=21.3.0" -e2b = "^1.5.4" +e2b = "^2.0.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" diff --git a/python/pytest.ini b/python/pytest.ini index e74467d5..7695413b 100644 --- a/python/pytest.ini +++ b/python/pytest.ini @@ -4,4 +4,4 @@ markers = skip_debug: skip test if E2B_DEBUG is set. asyncio_mode=auto -addopts = "--import-mode=importlib" "--numprocesses=1" +addopts = "--import-mode=importlib" "--numprocesses=2" diff --git a/python/tests/benchmarking.py b/python/tests/benchmarking.py index c78d674c..c3a4855c 100644 --- a/python/tests/benchmarking.py +++ b/python/tests/benchmarking.py @@ -14,7 +14,7 @@ for i in range(iterations): print("Iteration:", i + 1) start_time = time.time() - sandbox = Sandbox() + sandbox = Sandbox.create() create_sandbox_time += time.time() - start_time start_time = time.time() diff --git a/python/tests/conftest.py b/python/tests/conftest.py index daac9822..544f2ca5 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -17,7 +17,7 @@ def template(): @pytest.fixture() def sandbox(template, debug): - sandbox = Sandbox(template, timeout=timeout, debug=debug) + sandbox = Sandbox.create(template, timeout=timeout, debug=debug) try: yield sandbox diff --git a/python/tests/sync/env_vars/test_bash.py b/python/tests/sync/env_vars/test_bash.py index a77e9b54..9f5ea728 100644 --- a/python/tests/sync/env_vars/test_bash.py +++ b/python/tests/sync/env_vars/test_bash.py @@ -1,46 +1,34 @@ import pytest from e2b_code_interpreter import Sandbox + @pytest.mark.skip_debug() def test_env_vars_on_sandbox(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: - result = sandbox.run_code( - "echo $TEST_ENV_VAR", - language="bash" - ) + result = sandbox.run_code("echo $TEST_ENV_VAR", language="bash") assert result.logs.stdout[0] == "supertest\n" finally: sandbox.kill() + def test_env_vars_per_execution(sandbox: Sandbox): - result = sandbox.run_code( - "echo $FOO", - envs={"FOO": "bar"}, - language="bash" - ) - - result_empty = sandbox.run_code( - "echo ${FOO:-default}", - language="bash" - ) - + result = sandbox.run_code("echo $FOO", envs={"FOO": "bar"}, language="bash") + + result_empty = sandbox.run_code("echo ${FOO:-default}", language="bash") + assert result.logs.stdout[0] == "bar\n" assert result_empty.logs.stdout[0] == "default\n" + @pytest.mark.skip_debug() def test_env_vars_overwrite(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: result = sandbox.run_code( - "echo $TEST_ENV_VAR", - language="bash", - envs={"TEST_ENV_VAR": "overwrite"} - ) - result_global_default = sandbox.run_code( - "echo $TEST_ENV_VAR", - language="bash" + "echo $TEST_ENV_VAR", language="bash", envs={"TEST_ENV_VAR": "overwrite"} ) + result_global_default = sandbox.run_code("echo $TEST_ENV_VAR", language="bash") assert result.logs.stdout[0] == "overwrite\n" assert result_global_default.logs.stdout[0] == "supertest\n" finally: diff --git a/python/tests/sync/env_vars/test_java.py b/python/tests/sync/env_vars/test_java.py index 21a804e5..b0950d9d 100644 --- a/python/tests/sync/env_vars/test_java.py +++ b/python/tests/sync/env_vars/test_java.py @@ -1,32 +1,28 @@ import pytest from e2b_code_interpreter import Sandbox + @pytest.mark.skip_debug() def test_env_vars_on_sandbox(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: - result = sandbox.run_code( - 'System.getProperty("TEST_ENV_VAR")', - language="java" - ) + result = sandbox.run_code('System.getProperty("TEST_ENV_VAR")', language="java") assert result.text is not None assert result.text.strip() == "supertest" finally: sandbox.kill() + def test_env_vars_per_execution(sandbox: Sandbox): try: result = sandbox.run_code( - 'System.getProperty("FOO")', - envs={"FOO": "bar"}, - language="java" + 'System.getProperty("FOO")', envs={"FOO": "bar"}, language="java" ) - + result_empty = sandbox.run_code( - 'System.getProperty("FOO", "default")', - language="java" + 'System.getProperty("FOO", "default")', language="java" ) - + assert result.text is not None assert result.text.strip() == "bar" assert result_empty.text is not None @@ -34,18 +30,18 @@ def test_env_vars_per_execution(sandbox: Sandbox): finally: sandbox.kill() + @pytest.mark.skip_debug() def test_env_vars_overwrite(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: result = sandbox.run_code( 'System.getProperty("TEST_ENV_VAR")', language="java", - envs={"TEST_ENV_VAR": "overwrite"} + envs={"TEST_ENV_VAR": "overwrite"}, ) result_global_default = sandbox.run_code( - 'System.getProperty("TEST_ENV_VAR")', - language="java" + 'System.getProperty("TEST_ENV_VAR")', language="java" ) assert result.text is not None assert result.text.strip() == "overwrite" diff --git a/python/tests/sync/env_vars/test_js.py b/python/tests/sync/env_vars/test_js.py index 224740ce..6561d65b 100644 --- a/python/tests/sync/env_vars/test_js.py +++ b/python/tests/sync/env_vars/test_js.py @@ -1,32 +1,28 @@ import pytest from e2b_code_interpreter import Sandbox + @pytest.mark.skip_debug() def test_env_vars_on_sandbox(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: - result = sandbox.run_code( - "process.env.TEST_ENV_VAR", - language="javascript" - ) + result = sandbox.run_code("process.env.TEST_ENV_VAR", language="javascript") assert result.text is not None assert result.text.strip() == "supertest" finally: sandbox.kill() + def test_env_vars_per_execution(sandbox: Sandbox): try: result = sandbox.run_code( - "process.env.FOO", - envs={"FOO": "bar"}, - language="javascript" + "process.env.FOO", envs={"FOO": "bar"}, language="javascript" ) - + result_empty = sandbox.run_code( - "process.env.FOO || 'default'", - language="javascript" + "process.env.FOO || 'default'", language="javascript" ) - + assert result.text is not None assert result.text.strip() == "bar" assert result_empty.text is not None @@ -34,18 +30,18 @@ def test_env_vars_per_execution(sandbox: Sandbox): finally: sandbox.kill() + @pytest.mark.skip_debug() def test_env_vars_overwrite(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: result = sandbox.run_code( "process.env.TEST_ENV_VAR", language="javascript", - envs={"TEST_ENV_VAR": "overwrite"} + envs={"TEST_ENV_VAR": "overwrite"}, ) result_global_default = sandbox.run_code( - "process.env.TEST_ENV_VAR", - language="javascript" + "process.env.TEST_ENV_VAR", language="javascript" ) assert result.text is not None assert result.text.strip() == "overwrite" diff --git a/python/tests/sync/env_vars/test_python.py b/python/tests/sync/env_vars/test_python.py index 1d54d0c5..95170c75 100644 --- a/python/tests/sync/env_vars/test_python.py +++ b/python/tests/sync/env_vars/test_python.py @@ -1,32 +1,30 @@ import pytest from e2b_code_interpreter import Sandbox + @pytest.mark.skip_debug() def test_env_vars_on_sandbox(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: result = sandbox.run_code( - "import os; os.getenv('TEST_ENV_VAR')", - language="python" + "import os; os.getenv('TEST_ENV_VAR')", language="python" ) assert result.text is not None assert result.text.strip() == "supertest" finally: sandbox.kill() + def test_env_vars_per_execution(sandbox: Sandbox): try: result = sandbox.run_code( - "import os; os.getenv('FOO')", - envs={"FOO": "bar"}, - language="python" + "import os; os.getenv('FOO')", envs={"FOO": "bar"}, language="python" ) - + result_empty = sandbox.run_code( - "import os; os.getenv('FOO', 'default')", - language="python" + "import os; os.getenv('FOO', 'default')", language="python" ) - + assert result.text is not None assert result.text.strip() == "bar" assert result_empty.text is not None @@ -34,18 +32,18 @@ def test_env_vars_per_execution(sandbox: Sandbox): finally: sandbox.kill() + @pytest.mark.skip_debug() def test_env_vars_overwrite(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: result = sandbox.run_code( "import os; os.getenv('TEST_ENV_VAR')", language="python", - envs={"TEST_ENV_VAR": "overwrite"} + envs={"TEST_ENV_VAR": "overwrite"}, ) result_global_default = sandbox.run_code( - "import os; os.getenv('TEST_ENV_VAR')", - language="python" + "import os; os.getenv('TEST_ENV_VAR')", language="python" ) assert result.text is not None assert result.text.strip() == "overwrite" diff --git a/python/tests/sync/env_vars/test_r.py b/python/tests/sync/env_vars/test_r.py index a2984755..c1641116 100644 --- a/python/tests/sync/env_vars/test_r.py +++ b/python/tests/sync/env_vars/test_r.py @@ -1,32 +1,28 @@ import pytest from e2b_code_interpreter import Sandbox + @pytest.mark.skip_debug() def test_env_vars_on_sandbox(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: - result = sandbox.run_code( - "Sys.getenv('TEST_ENV_VAR')", - language="r" - ) + result = sandbox.run_code("Sys.getenv('TEST_ENV_VAR')", language="r") assert result.results[0].text is not None assert result.results[0].text.strip() == '[1] "supertest"' finally: sandbox.kill() + def test_env_vars_per_execution(sandbox: Sandbox): try: result = sandbox.run_code( - "Sys.getenv('FOO')", - envs={"FOO": "bar"}, - language="r" + "Sys.getenv('FOO')", envs={"FOO": "bar"}, language="r" ) - + result_empty = sandbox.run_code( - "Sys.getenv('FOO', unset = 'default')", - language="r" + "Sys.getenv('FOO', unset = 'default')", language="r" ) - + assert result.results[0].text is not None assert result.results[0].text.strip() == '[1] "bar"' assert result_empty.results[0].text is not None @@ -34,18 +30,18 @@ def test_env_vars_per_execution(sandbox: Sandbox): finally: sandbox.kill() + @pytest.mark.skip_debug() def test_env_vars_overwrite(template): - sandbox = Sandbox(template=template, envs={"TEST_ENV_VAR": "supertest"}) + sandbox = Sandbox.create(template=template, envs={"TEST_ENV_VAR": "supertest"}) try: result = sandbox.run_code( "Sys.getenv('TEST_ENV_VAR')", language="r", - envs={"TEST_ENV_VAR": "overwrite"} + envs={"TEST_ENV_VAR": "overwrite"}, ) result_global_default = sandbox.run_code( - "Sys.getenv('TEST_ENV_VAR')", - language="r" + "Sys.getenv('TEST_ENV_VAR')", language="r" ) assert result.results[0].text is not None assert result.results[0].text.strip() == '[1] "overwrite"' diff --git a/template/README.md b/template/README.md index 6e70302e..c1103acd 100644 --- a/template/README.md +++ b/template/README.md @@ -24,12 +24,12 @@ If you want to customize the Code Interprerter sandbox (e.g.: add a preinstalled **Python** ```python from e2b_code_interpreter import Sandbox - sandbox = Sandbox(template="your-custom-sandbox-name") + sandbox = Sandbox.create(template="your-custom-sandbox-name") execution = sandbox.run_code("print('hello')") sandbox.kill() # Or you can use `with` which handles closing the sandbox for you - with Sandbox(template="your-custom-sandbox-name") as sandbox: + with Sandbox.create(template="your-custom-sandbox-name") as sandbox: execution = sandbox.run_code("print('hello')") ``` diff --git a/template/e2b.toml b/template/e2b.toml index a488e5d9..1eb93ae1 100644 --- a/template/e2b.toml +++ b/template/e2b.toml @@ -3,7 +3,7 @@ # Python SDK # from e2b import Sandbox, AsyncSandbox -# sandbox = Sandbox("code-interpreter-v1") # Sync sandbox +# sandbox = Sandbox.create("code-interpreter-v1") # Sync sandbox # sandbox = await AsyncSandbox.create("code-interpreter-v1") # Async sandbox # JS SDK diff --git a/template/server/envs.py b/template/server/envs.py index 2456f74d..8752e7b9 100644 --- a/template/server/envs.py +++ b/template/server/envs.py @@ -1,4 +1,5 @@ import os +from typing import Optional import httpx @@ -6,11 +7,14 @@ ENVD_PORT = 49983 -async def get_envs() -> dict: +async def get_envs(access_token: Optional[str]) -> dict: if LOCAL: - return { - "E2B_TEST_VARIABLE": "true" - } + return {"E2B_TEST_VARIABLE": "true"} async with httpx.AsyncClient() as client: - response = await client.get(f"http://localhost:{ENVD_PORT}/envs") + headers = {} + if access_token: + headers["X-Access-Token"] = f"{access_token}" + response = await client.get( + f"http://localhost:{ENVD_PORT}/envs", headers=headers + ) return response.json() diff --git a/template/server/main.py b/template/server/main.py index 86e57e17..917c87ab 100644 --- a/template/server/main.py +++ b/template/server/main.py @@ -1,12 +1,11 @@ import logging import sys -import uuid import httpx from typing import Dict, Union, Literal, Set from contextlib import asynccontextmanager -from fastapi import FastAPI +from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse from api.models.context import Context @@ -17,7 +16,6 @@ from messaging import ContextWebSocket from stream import StreamingListJsonResponse from utils.locks import LockedMap -from envs import get_envs logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) logger = logging.Logger(__name__) @@ -36,11 +34,15 @@ async def lifespan(app: FastAPI): client = httpx.AsyncClient() try: - python_context = await create_context(client, websockets, "python", "/home/user") + python_context = await create_context( + client, websockets, "python", "/home/user" + ) default_websockets["python"] = python_context.id websockets["default"] = websockets[python_context.id] - javascript_context = await create_context(client, websockets, "javascript", "/home/user") + javascript_context = await create_context( + client, websockets, "javascript", "/home/user" + ) default_websockets["javascript"] = javascript_context.id logger.info("Connected to default runtime") @@ -67,18 +69,18 @@ async def get_health(): @app.post("/execute") -async def post_execute(request: ExecutionRequest): - logger.info(f"Executing code: {request.code}") +async def post_execute(request: Request, exec_request: ExecutionRequest): + logger.info(f"Executing code: {exec_request.code}") - if request.context_id and request.language: + if exec_request.context_id and exec_request.language: return PlainTextResponse( "Only one of context_id or language can be provided", status_code=400, ) context_id = None - if request.language: - language = normalize_language(request.language) + if exec_request.language: + language = normalize_language(exec_request.language) async with await default_websockets.get_lock(language): context_id = default_websockets.get(language) @@ -94,8 +96,8 @@ async def post_execute(request: ExecutionRequest): context_id = context.id default_websockets[language] = context_id - elif request.context_id: - context_id = request.context_id + elif exec_request.context_id: + context_id = exec_request.context_id if context_id: ws = websockets.get(context_id, None) @@ -104,14 +106,15 @@ async def post_execute(request: ExecutionRequest): if not ws: return PlainTextResponse( - f"Context {request.context_id} not found", + f"Context {exec_request.context_id} not found", status_code=404, ) return StreamingListJsonResponse( ws.execute( - request.code, - env_vars=request.env_vars, + exec_request.code, + env_vars=exec_request.env_vars, + access_token=request.headers.get("X-Access-Token", None), ) ) diff --git a/template/server/messaging.py b/template/server/messaging.py index 7c295b2d..e541351f 100644 --- a/template/server/messaging.py +++ b/template/server/messaging.py @@ -27,6 +27,7 @@ logger = logging.getLogger(__name__) + class Execution: def __init__(self, in_background: bool = False): self.queue = Queue[ @@ -51,13 +52,7 @@ class ContextWebSocket: _global_env_vars: Optional[Dict[StrictStr, str]] = None _cleanup_task: Optional[asyncio.Task] = None - def __init__( - self, - context_id: str, - session_id: str, - language: str, - cwd: str - ): + def __init__(self, context_id: str, session_id: str, language: str, cwd: str): self.language = language self.cwd = cwd self.context_id = context_id @@ -155,13 +150,13 @@ def _set_env_vars_code(self, env_vars: Dict[StrictStr, str]) -> str: command = self._set_env_var_snippet(k, v) if command: env_commands.append(command) - + return "\n".join(env_commands) def _reset_env_vars_code(self, env_vars: Dict[StrictStr, str]) -> str: """Build environment variable cleanup code for the current language.""" cleanup_commands = [] - + for key in env_vars: # Check if this var exists in global env vars if self._global_env_vars and key in self._global_env_vars: @@ -171,39 +166,39 @@ def _reset_env_vars_code(self, env_vars: Dict[StrictStr, str]) -> str: else: # Remove the variable command = self._delete_env_var_snippet(key) - + if command: cleanup_commands.append(command) - + return "\n".join(cleanup_commands) def _get_code_indentation(self, code: str) -> str: """Get the indentation from the first non-empty line of code.""" if not code or not code.strip(): return "" - - lines = code.split('\n') + + lines = code.split("\n") for line in lines: if line.strip(): # First non-empty line - return line[:len(line) - len(line.lstrip())] - + return line[: len(line) - len(line.lstrip())] + return "" def _indent_code_with_level(self, code: str, indent_level: str) -> str: """Apply the given indentation level to each line of code.""" if not code or not indent_level: return code - - lines = code.split('\n') + + lines = code.split("\n") indented_lines = [] - + for line in lines: if line.strip(): # Non-empty lines indented_lines.append(indent_level + line) else: indented_lines.append(line) - - return '\n'.join(indented_lines) + + return "\n".join(indented_lines) async def _cleanup_env_vars(self, env_vars: Dict[StrictStr, str]): """Clean up environment variables in a separate execution request.""" @@ -276,7 +271,8 @@ async def change_current_directory( async def execute( self, code: Union[str, StrictStr], - env_vars: Dict[StrictStr, str] = None, + env_vars: Dict[StrictStr, str], + access_token: str, ): message_id = str(uuid.uuid4()) self._executions[message_id] = Execution() @@ -294,28 +290,32 @@ async def execute( logger.warning(f"Cleanup task failed: {e}") finally: self._cleanup_task = None - + # Get the indentation level from the code code_indent = self._get_code_indentation(code) - + # Build the complete code snippet with env vars complete_code = code - + global_env_vars_snippet = "" env_vars_snippet = "" if self._global_env_vars is None: - self._global_env_vars = await get_envs() + self._global_env_vars = await get_envs(access_token=access_token) global_env_vars_snippet = self._set_env_vars_code(self._global_env_vars) - + if env_vars: env_vars_snippet = self._set_env_vars_code(env_vars) if global_env_vars_snippet or env_vars_snippet: - indented_env_code = self._indent_code_with_level(f"{global_env_vars_snippet}\n{env_vars_snippet}", code_indent) + indented_env_code = self._indent_code_with_level( + f"{global_env_vars_snippet}\n{env_vars_snippet}", code_indent + ) complete_code = f"{indented_env_code}\n{complete_code}" - logger.info(f"Sending code for the execution ({message_id}): {complete_code}") + logger.info( + f"Sending code for the execution ({message_id}): {complete_code}" + ) request = self._get_execute_request(message_id, complete_code, False) # Send the code for execution @@ -329,7 +329,9 @@ async def execute( # Clean up env vars in a separate request after the main code has run if env_vars: - self._cleanup_task = asyncio.create_task(self._cleanup_env_vars(env_vars)) + self._cleanup_task = asyncio.create_task( + self._cleanup_env_vars(env_vars) + ) async def _receive_message(self): if not self._ws: @@ -396,7 +398,9 @@ async def _process_message(self, data: dict): elif data["msg_type"] == "stream": if data["content"]["name"] == "stdout": - logger.debug(f"Execution {parent_msg_ig} received stdout: {data['content']['text']}") + logger.debug( + f"Execution {parent_msg_ig} received stdout: {data['content']['text']}" + ) await queue.put( Stdout( text=data["content"]["text"], timestamp=data["header"]["date"] @@ -404,7 +408,9 @@ async def _process_message(self, data: dict): ) elif data["content"]["name"] == "stderr": - logger.debug(f"Execution {parent_msg_ig} received stderr: {data['content']['text']}") + logger.debug( + f"Execution {parent_msg_ig} received stderr: {data['content']['text']}" + ) await queue.put( Stderr( text=data["content"]["text"], timestamp=data["header"]["date"]