e2b-code-interpreter 2.8.0__tar.gz → 2.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: e2b-code-interpreter
3
+ Version: 2.11.0
4
+ Summary: E2B Code Interpreter - Stateful code execution
5
+ Author: e2b
6
+ Author-email: e2b <hello@e2b.dev>
7
+ License-Expression: MIT
8
+ Requires-Dist: httpx>=0.20.0,<1.0.0
9
+ Requires-Dist: attrs>=21.3.0
10
+ Requires-Dist: e2b>=2.55.1,<3.0.0
11
+ Requires-Python: >=3.11
12
+ Project-URL: Homepage, https://e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=package_homepage&utm_content=e2b-code-interpreter
13
+ Project-URL: Repository, https://github.com/e2b-dev/e2b/tree/main/packages/code-interpreter-python
14
+ Project-URL: Bug Tracker, https://github.com/e2b-dev/e2b/issues
15
+ Description-Content-Type: text/markdown
16
+
17
+ <p align="center">
18
+ <picture>
19
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-white.png">
20
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-black.png">
21
+ <img alt="E2B Logo" src="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-black.png" width="200">
22
+ </picture>
23
+ </p>
24
+
25
+ <h4 align="center">
26
+ <a href="https://pypi.org/project/e2b/">
27
+ <img alt="Last 1 month downloads for the Python SDK" loading="lazy" width="200" height="20" decoding="async" data-nimg="1"
28
+ style="color:transparent;width:auto;height:100%" src="https://img.shields.io/pypi/dm/e2b?label=PyPI%20Downloads">
29
+ </a>
30
+ </h4>
31
+
32
+ <!---
33
+ <img width="100%" src="/readme-assets/preview.png" alt="Cover image">
34
+ --->
35
+ ## What is E2B?
36
+ [E2B](https://e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter) is an open-source infrastructure that allows you run to AI-generated code in secure isolated sandboxes in the cloud. To start and control sandboxes, use our [JavaScript SDK](https://www.npmjs.com/package/@e2b/code-interpreter) or [Python SDK](https://pypi.org/project/e2b_code_interpreter).
37
+
38
+ Source: [packages/code-interpreter-python](https://github.com/e2b-dev/E2B/tree/main/packages/code-interpreter-python)
39
+
40
+ ## Run your first Sandbox
41
+
42
+ ### 1. Install SDK
43
+
44
+ ```
45
+ pip install e2b-code-interpreter
46
+ ```
47
+
48
+ ### 2. Get your E2B API key
49
+ 1. Sign up to E2B [here](https://e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter).
50
+ 2. Get your API key [here](https://e2b.dev/dashboard?tab=keys&utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter).
51
+ 3. Set environment variable with your API key.
52
+ ```
53
+ E2B_API_KEY=e2b_***
54
+ ```
55
+
56
+ ### 3. Execute code with code interpreter inside Sandbox
57
+
58
+ ```py
59
+ from e2b_code_interpreter import Sandbox
60
+
61
+ with Sandbox.create() as sandbox:
62
+ sandbox.run_code("x = 1")
63
+ execution = sandbox.run_code("x+=1; x")
64
+ print(execution.text) # outputs 2
65
+ ```
66
+
67
+ ### 4. Bind the configuration to a client
68
+
69
+ The top-level `Sandbox` and `AsyncSandbox` exports read their configuration from the environment variables. To use an explicit configuration — e.g. several API keys or domains in one process — create an `E2B` client and use the resource classes it exposes:
70
+
71
+ ```py
72
+ from e2b_code_interpreter import E2B
73
+
74
+ client = E2B(api_key="e2b_***", domain="e2b.dev")
75
+
76
+ with client.Sandbox.create() as sandbox:
77
+ execution = sandbox.run_code("x = 1; x += 1; x")
78
+
79
+ # The async variant is exposed as well.
80
+ async_sandbox = await client.AsyncSandbox.create()
81
+
82
+ # The core resources are bound to the client's configuration as well.
83
+ volume = client.Volume.create("my-volume")
84
+ exists = client.Template.exists("my-template")
85
+ secret = client.Secret.create("openai-api-key", "sk-***")
86
+
87
+ # The classes can be assigned and used like the top-level ones.
88
+ Sandbox = client.Sandbox
89
+ paginator = Sandbox.list()
90
+ ```
91
+
92
+ Per-call params still take precedence over the client's params, and clients are isolated from each other and from the env-configured top-level exports.
93
+
94
+ ### 5. Check docs
95
+ Visit [E2B documentation](https://docs.e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter).
96
+
97
+ ### 6. E2B cookbook
98
+ Visit our [Cookbook](https://github.com/e2b-dev/e2b-cookbook/tree/main) to get inspired by examples with different LLMs and AI frameworks.
@@ -0,0 +1,82 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-white.png">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-black.png">
5
+ <img alt="E2B Logo" src="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-black.png" width="200">
6
+ </picture>
7
+ </p>
8
+
9
+ <h4 align="center">
10
+ <a href="https://pypi.org/project/e2b/">
11
+ <img alt="Last 1 month downloads for the Python SDK" loading="lazy" width="200" height="20" decoding="async" data-nimg="1"
12
+ style="color:transparent;width:auto;height:100%" src="https://img.shields.io/pypi/dm/e2b?label=PyPI%20Downloads">
13
+ </a>
14
+ </h4>
15
+
16
+ <!---
17
+ <img width="100%" src="/readme-assets/preview.png" alt="Cover image">
18
+ --->
19
+ ## What is E2B?
20
+ [E2B](https://e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter) is an open-source infrastructure that allows you run to AI-generated code in secure isolated sandboxes in the cloud. To start and control sandboxes, use our [JavaScript SDK](https://www.npmjs.com/package/@e2b/code-interpreter) or [Python SDK](https://pypi.org/project/e2b_code_interpreter).
21
+
22
+ Source: [packages/code-interpreter-python](https://github.com/e2b-dev/E2B/tree/main/packages/code-interpreter-python)
23
+
24
+ ## Run your first Sandbox
25
+
26
+ ### 1. Install SDK
27
+
28
+ ```
29
+ pip install e2b-code-interpreter
30
+ ```
31
+
32
+ ### 2. Get your E2B API key
33
+ 1. Sign up to E2B [here](https://e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter).
34
+ 2. Get your API key [here](https://e2b.dev/dashboard?tab=keys&utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter).
35
+ 3. Set environment variable with your API key.
36
+ ```
37
+ E2B_API_KEY=e2b_***
38
+ ```
39
+
40
+ ### 3. Execute code with code interpreter inside Sandbox
41
+
42
+ ```py
43
+ from e2b_code_interpreter import Sandbox
44
+
45
+ with Sandbox.create() as sandbox:
46
+ sandbox.run_code("x = 1")
47
+ execution = sandbox.run_code("x+=1; x")
48
+ print(execution.text) # outputs 2
49
+ ```
50
+
51
+ ### 4. Bind the configuration to a client
52
+
53
+ The top-level `Sandbox` and `AsyncSandbox` exports read their configuration from the environment variables. To use an explicit configuration — e.g. several API keys or domains in one process — create an `E2B` client and use the resource classes it exposes:
54
+
55
+ ```py
56
+ from e2b_code_interpreter import E2B
57
+
58
+ client = E2B(api_key="e2b_***", domain="e2b.dev")
59
+
60
+ with client.Sandbox.create() as sandbox:
61
+ execution = sandbox.run_code("x = 1; x += 1; x")
62
+
63
+ # The async variant is exposed as well.
64
+ async_sandbox = await client.AsyncSandbox.create()
65
+
66
+ # The core resources are bound to the client's configuration as well.
67
+ volume = client.Volume.create("my-volume")
68
+ exists = client.Template.exists("my-template")
69
+ secret = client.Secret.create("openai-api-key", "sk-***")
70
+
71
+ # The classes can be assigned and used like the top-level ones.
72
+ Sandbox = client.Sandbox
73
+ paginator = Sandbox.list()
74
+ ```
75
+
76
+ Per-call params still take precedence over the client's params, and clients are isolated from each other and from the env-configured top-level exports.
77
+
78
+ ### 5. Check docs
79
+ Visit [E2B documentation](https://docs.e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=readme&utm_content=code-interpreter).
80
+
81
+ ### 6. E2B cookbook
82
+ Visit our [Cookbook](https://github.com/e2b-dev/e2b-cookbook/tree/main) to get inspired by examples with different LLMs and AI frameworks.
@@ -1,4 +1,5 @@
1
1
  from e2b import *
2
+ from .client import E2B, E2BClientParams
2
3
  from .code_interpreter_sync import Sandbox
3
4
  from .code_interpreter_async import AsyncSandbox
4
5
  from .models import (
@@ -194,17 +194,19 @@ class BoxAndWhiskerChart(Chart2D):
194
194
  class SuperChart(Chart):
195
195
  type = ChartType.SUPERCHART
196
196
 
197
- elements: List[
198
- Union[LineChart, ScatterChart, BarChart, PieChart, BoxAndWhiskerChart]
199
- ]
197
+ elements: List[Chart]
200
198
 
201
199
  def __init__(self, **kwargs):
202
200
  super().__init__(**kwargs)
203
- self.elements = [_deserialize_chart(g) for g in kwargs["elements"]]
201
+ self.elements = []
202
+ for raw_chart in kwargs["elements"]:
203
+ chart = _deserialize_chart(raw_chart)
204
+ if chart is not None:
205
+ self.elements.append(chart)
204
206
 
205
207
 
206
208
  ChartTypes = Union[
207
- LineChart, ScatterChart, BarChart, PieChart, BoxAndWhiskerChart, SuperChart
209
+ Chart, LineChart, ScatterChart, BarChart, PieChart, BoxAndWhiskerChart, SuperChart
208
210
  ]
209
211
 
210
212
 
@@ -0,0 +1,93 @@
1
+ from typing import Dict, Type, TypeVar, cast
2
+
3
+ from e2b import ApiParams, E2BClientParams
4
+ from e2b import E2B as CoreE2B
5
+ from typing_extensions import Unpack
6
+
7
+ from e2b_code_interpreter.code_interpreter_async import AsyncSandbox
8
+ from e2b_code_interpreter.code_interpreter_sync import Sandbox
9
+
10
+ T = TypeVar("T")
11
+
12
+ __all__ = ["E2B", "E2BClientParams"]
13
+
14
+
15
+ def _bind(cls: Type[T], api_params: ApiParams) -> Type[T]:
16
+ """Generate a subclass of ``cls`` carrying ``api_params`` as its bound params."""
17
+ return cast(
18
+ Type[T],
19
+ type(cls.__name__, (cls,), {"_bound_api_params": api_params}),
20
+ )
21
+
22
+
23
+ class E2B:
24
+ """
25
+ E2B client with an explicitly bound connection configuration.
26
+
27
+ The resource classes exposed by the client (`Sandbox`, `AsyncSandbox`,
28
+ `Volume`, `AsyncVolume`, `Template`, `AsyncTemplate`, `Secret`,
29
+ `AsyncSecret`) behave exactly like the top-level exports of the same name,
30
+ except the params passed to the client are used as the defaults instead of
31
+ the environment variables.
32
+ Per-call params still take precedence over the client's params.
33
+
34
+ Multiple clients are fully isolated from each other and from the top-level
35
+ env-configured exports.
36
+
37
+ Example:
38
+ ```python
39
+ from e2b_code_interpreter import E2B
40
+
41
+ client = E2B(api_key="e2b_...", domain="e2b.dev")
42
+
43
+ sandbox = client.Sandbox.create()
44
+ execution = sandbox.run_code("x = 1; x += 1; x")
45
+ ```
46
+ """
47
+
48
+ def __init__(self, **opts: Unpack[E2BClientParams]):
49
+ """
50
+ Create a new client with the API params bound to it.
51
+
52
+ :param opts: API params used as the defaults for every call made
53
+ through this client's resource classes.
54
+ """
55
+ # Params are copied so later mutations of the caller's dicts cannot
56
+ # change the bound configuration.
57
+ api_params = cast(ApiParams, dict(cast(Dict[str, object], opts)))
58
+
59
+ headers = api_params.get("headers")
60
+ if headers is not None:
61
+ api_params["headers"] = dict(headers)
62
+
63
+ api_headers = api_params.get("api_headers")
64
+ if api_headers is not None:
65
+ api_params["api_headers"] = dict(api_headers)
66
+
67
+ self.Sandbox = _bind(Sandbox, api_params)
68
+ """Code Interpreter `Sandbox` class bound to this client's connection configuration."""
69
+
70
+ self.AsyncSandbox = _bind(AsyncSandbox, api_params)
71
+ """Code Interpreter `AsyncSandbox` class bound to this client's connection configuration."""
72
+
73
+ # The resources that are not specific to the Code Interpreter are bound
74
+ # by the core client.
75
+ core = CoreE2B(**api_params)
76
+
77
+ self.Volume = core.Volume
78
+ """`Volume` class bound to this client's connection configuration."""
79
+
80
+ self.AsyncVolume = core.AsyncVolume
81
+ """`AsyncVolume` class bound to this client's connection configuration."""
82
+
83
+ self.Template = core.Template
84
+ """`Template` class bound to this client's connection configuration."""
85
+
86
+ self.AsyncTemplate = core.AsyncTemplate
87
+ """`AsyncTemplate` class bound to this client's connection configuration."""
88
+
89
+ self.Secret = core.Secret
90
+ """`Secret` class bound to this client's connection configuration."""
91
+
92
+ self.AsyncSecret = core.AsyncSecret
93
+ """`AsyncSecret` class bound to this client's connection configuration."""
@@ -29,6 +29,7 @@ from e2b_code_interpreter.models import (
29
29
  from e2b_code_interpreter.exceptions import (
30
30
  format_execution_timeout_error,
31
31
  format_request_timeout_error,
32
+ format_sandbox_killed_error,
32
33
  )
33
34
 
34
35
  logger = logging.getLogger(__name__)
@@ -60,14 +61,16 @@ class AsyncSandbox(BaseAsyncSandbox):
60
61
 
61
62
  @property
62
63
  def _jupyter_url(self) -> str:
63
- return f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(JUPYTER_PORT)}"
64
+ return self.connection_config.get_sandbox_url(
65
+ self.sandbox_id, self.sandbox_domain, port=JUPYTER_PORT
66
+ )
64
67
 
65
68
  @property
66
69
  def _client(self) -> AsyncClient:
67
70
  # TODO: Remove later
68
71
  # Use a dedicated HTTP/1.1 transport for Jupyter requests.
69
72
  #
70
- # The base SDK's shared transport now defaults to http2=True. With
73
+ # The base SDK's shared transport now defaults to HTTP/2. With
71
74
  # HTTP/2, multiple requests are multiplexed over a single TCP
72
75
  # connection, so when a client cancels a request (e.g. the caller
73
76
  # disconnects from the streaming `/execute` endpoint) the server
@@ -80,9 +83,26 @@ class AsyncSandbox(BaseAsyncSandbox):
80
83
  # cancelled reliably. The helper also caches the transport
81
84
  # per-event-loop for async.
82
85
  return AsyncClient(
83
- transport=get_transport(self.connection_config, http2=False),
86
+ transport=get_transport(self.connection_config, http_version="1.1"),
84
87
  )
85
88
 
89
+ async def _handle_connection_error(self, err: Exception) -> None:
90
+ """
91
+ Raises a descriptive exception if the connection error was caused by
92
+ the sandbox being killed mid-request. If the sandbox is still running
93
+ (or its state can't be determined), returns so the caller can re-raise
94
+ the original error.
95
+ """
96
+ try:
97
+ running = await self.is_running()
98
+ except Exception:
99
+ # The state check itself failed, so we can't tell whether the
100
+ # sandbox was killed — let the caller re-raise the original error
101
+ # instead of wrongly claiming the sandbox is gone.
102
+ return
103
+ if not running:
104
+ raise format_sandbox_killed_error() from err
105
+
86
106
  @overload
87
107
  async def run_code(
88
108
  self,
@@ -178,6 +198,8 @@ class AsyncSandbox(BaseAsyncSandbox):
178
198
  try:
179
199
  headers = {
180
200
  "Content-Type": "application/json",
201
+ "E2b-Sandbox-Id": self.sandbox_id,
202
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
181
203
  }
182
204
  if self._envd_access_token:
183
205
  headers["X-Access-Token"] = self._envd_access_token
@@ -194,7 +216,22 @@ class AsyncSandbox(BaseAsyncSandbox):
194
216
  "env_vars": envs,
195
217
  },
196
218
  headers=headers,
197
- timeout=(request_timeout, timeout, request_timeout, request_timeout),
219
+ # `timeout` bounds the execution, `request_timeout` only the
220
+ # connect. Every non-connect phase must carry `timeout`: the
221
+ # SDK's pyqwest-backed transport collapses the per-phase
222
+ # timeouts into a single whole-request deadline and takes the
223
+ # longest of them, so leaving `request_timeout` on the write
224
+ # and pool phases would raise the floor to
225
+ # `max(timeout, request_timeout)` and silently ignore any
226
+ # `timeout` shorter than it. This matches the JS SDK, which
227
+ # aborts the execution on a `timeout`-long timer.
228
+ # `timeout=0` disables the deadline entirely; the transport
229
+ # still bounds connect on its own.
230
+ timeout=(
231
+ httpx.Timeout(timeout, connect=request_timeout)
232
+ if timeout is not None
233
+ else httpx.Timeout(None)
234
+ ),
198
235
  ) as response:
199
236
  err = await aextract_exception(response)
200
237
  if err:
@@ -217,6 +254,9 @@ class AsyncSandbox(BaseAsyncSandbox):
217
254
  raise format_execution_timeout_error()
218
255
  except httpx.TimeoutException:
219
256
  raise format_request_timeout_error()
257
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
258
+ await self._handle_connection_error(err)
259
+ raise
220
260
 
221
261
  async def create_code_context(
222
262
  self,
@@ -244,6 +284,8 @@ class AsyncSandbox(BaseAsyncSandbox):
244
284
  try:
245
285
  headers = {
246
286
  "Content-Type": "application/json",
287
+ "E2b-Sandbox-Id": self.sandbox_id,
288
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
247
289
  }
248
290
  if self.traffic_access_token:
249
291
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -263,6 +305,9 @@ class AsyncSandbox(BaseAsyncSandbox):
263
305
  return Context.from_json(data)
264
306
  except httpx.TimeoutException:
265
307
  raise format_request_timeout_error()
308
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
309
+ await self._handle_connection_error(err)
310
+ raise
266
311
 
267
312
  async def remove_code_context(
268
313
  self,
@@ -280,6 +325,8 @@ class AsyncSandbox(BaseAsyncSandbox):
280
325
  try:
281
326
  headers = {
282
327
  "Content-Type": "application/json",
328
+ "E2b-Sandbox-Id": self.sandbox_id,
329
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
283
330
  }
284
331
  if self.traffic_access_token:
285
332
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -295,6 +342,9 @@ class AsyncSandbox(BaseAsyncSandbox):
295
342
  raise err
296
343
  except httpx.TimeoutException:
297
344
  raise format_request_timeout_error()
345
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
346
+ await self._handle_connection_error(err)
347
+ raise
298
348
 
299
349
  async def list_code_contexts(self) -> List[Context]:
300
350
  """
@@ -305,6 +355,8 @@ class AsyncSandbox(BaseAsyncSandbox):
305
355
  try:
306
356
  headers = {
307
357
  "Content-Type": "application/json",
358
+ "E2b-Sandbox-Id": self.sandbox_id,
359
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
308
360
  }
309
361
  if self.traffic_access_token:
310
362
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -323,6 +375,9 @@ class AsyncSandbox(BaseAsyncSandbox):
323
375
  return [Context.from_json(context_data) for context_data in data]
324
376
  except httpx.TimeoutException:
325
377
  raise format_request_timeout_error()
378
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
379
+ await self._handle_connection_error(err)
380
+ raise
326
381
 
327
382
  async def restart_code_context(
328
383
  self,
@@ -339,6 +394,8 @@ class AsyncSandbox(BaseAsyncSandbox):
339
394
  try:
340
395
  headers = {
341
396
  "Content-Type": "application/json",
397
+ "E2b-Sandbox-Id": self.sandbox_id,
398
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
342
399
  }
343
400
  if self.traffic_access_token:
344
401
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -354,3 +411,6 @@ class AsyncSandbox(BaseAsyncSandbox):
354
411
  raise err
355
412
  except httpx.TimeoutException:
356
413
  raise format_request_timeout_error()
414
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
415
+ await self._handle_connection_error(err)
416
+ raise
@@ -25,6 +25,7 @@ from e2b_code_interpreter.models import (
25
25
  from e2b_code_interpreter.exceptions import (
26
26
  format_execution_timeout_error,
27
27
  format_request_timeout_error,
28
+ format_sandbox_killed_error,
28
29
  )
29
30
 
30
31
  logger = logging.getLogger(__name__)
@@ -57,14 +58,16 @@ class Sandbox(BaseSandbox):
57
58
 
58
59
  @property
59
60
  def _jupyter_url(self) -> str:
60
- return f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(JUPYTER_PORT)}"
61
+ return self.connection_config.get_sandbox_url(
62
+ self.sandbox_id, self.sandbox_domain, port=JUPYTER_PORT
63
+ )
61
64
 
62
65
  @property
63
66
  def _client(self) -> Client:
64
67
  # TODO: Remove later
65
68
  # Use a dedicated HTTP/1.1 transport for Jupyter requests.
66
69
  #
67
- # The base SDK's shared transport now defaults to http2=True. With
70
+ # The base SDK's shared transport now defaults to HTTP/2. With
68
71
  # HTTP/2, multiple requests are multiplexed over a single TCP
69
72
  # connection, so when a client cancels a request (e.g. the caller
70
73
  # disconnects from the streaming `/execute` endpoint) the server
@@ -75,7 +78,26 @@ class Sandbox(BaseSandbox):
75
78
  # connection and request, so client disconnects propagate to the
76
79
  # server as a TCP close and long-running executions can be
77
80
  # cancelled reliably.
78
- return Client(transport=get_transport(self.connection_config, http2=False))
81
+ return Client(
82
+ transport=get_transport(self.connection_config, http_version="1.1")
83
+ )
84
+
85
+ def _handle_connection_error(self, err: Exception) -> None:
86
+ """
87
+ Raises a descriptive exception if the connection error was caused by
88
+ the sandbox being killed mid-request. If the sandbox is still running
89
+ (or its state can't be determined), returns so the caller can re-raise
90
+ the original error.
91
+ """
92
+ try:
93
+ running = self.is_running()
94
+ except Exception:
95
+ # The state check itself failed, so we can't tell whether the
96
+ # sandbox was killed — let the caller re-raise the original error
97
+ # instead of wrongly claiming the sandbox is gone.
98
+ return
99
+ if not running:
100
+ raise format_sandbox_killed_error() from err
79
101
 
80
102
  @overload
81
103
  def run_code(
@@ -171,7 +193,11 @@ class Sandbox(BaseSandbox):
171
193
  context_id = context.id if context else None
172
194
 
173
195
  try:
174
- headers: Dict[str, str] = {"Content-Type": "application/json"}
196
+ headers: Dict[str, str] = {
197
+ "Content-Type": "application/json",
198
+ "E2b-Sandbox-Id": self.sandbox_id,
199
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
200
+ }
175
201
  if self._envd_access_token:
176
202
  headers["X-Access-Token"] = self._envd_access_token
177
203
  if self.traffic_access_token:
@@ -187,7 +213,22 @@ class Sandbox(BaseSandbox):
187
213
  "env_vars": envs,
188
214
  },
189
215
  headers=headers,
190
- timeout=(request_timeout, timeout, request_timeout, request_timeout),
216
+ # `timeout` bounds the execution, `request_timeout` only the
217
+ # connect. Every non-connect phase must carry `timeout`: the
218
+ # SDK's pyqwest-backed transport collapses the per-phase
219
+ # timeouts into a single whole-request deadline and takes the
220
+ # longest of them, so leaving `request_timeout` on the write
221
+ # and pool phases would raise the floor to
222
+ # `max(timeout, request_timeout)` and silently ignore any
223
+ # `timeout` shorter than it. This matches the JS SDK, which
224
+ # aborts the execution on a `timeout`-long timer.
225
+ # `timeout=0` disables the deadline entirely; the transport
226
+ # still bounds connect on its own.
227
+ timeout=(
228
+ httpx.Timeout(timeout, connect=request_timeout)
229
+ if timeout is not None
230
+ else httpx.Timeout(None)
231
+ ),
191
232
  ) as response:
192
233
  err = extract_exception(response)
193
234
  if err:
@@ -210,6 +251,9 @@ class Sandbox(BaseSandbox):
210
251
  raise format_execution_timeout_error()
211
252
  except httpx.TimeoutException:
212
253
  raise format_request_timeout_error()
254
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
255
+ self._handle_connection_error(err)
256
+ raise
213
257
 
214
258
  def create_code_context(
215
259
  self,
@@ -235,9 +279,11 @@ class Sandbox(BaseSandbox):
235
279
  data["cwd"] = cwd
236
280
 
237
281
  try:
238
- headers: Dict[str, str] = {"Content-Type": "application/json"}
239
- if self._envd_access_token:
240
- headers["X-Access-Token"] = self._envd_access_token
282
+ headers: Dict[str, str] = {
283
+ "Content-Type": "application/json",
284
+ "E2b-Sandbox-Id": self.sandbox_id,
285
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
286
+ }
241
287
  if self.traffic_access_token:
242
288
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
243
289
 
@@ -256,6 +302,9 @@ class Sandbox(BaseSandbox):
256
302
  return Context.from_json(data)
257
303
  except httpx.TimeoutException:
258
304
  raise format_request_timeout_error()
305
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
306
+ self._handle_connection_error(err)
307
+ raise
259
308
 
260
309
  def remove_code_context(
261
310
  self,
@@ -271,9 +320,11 @@ class Sandbox(BaseSandbox):
271
320
  context_id = context.id if isinstance(context, Context) else context
272
321
 
273
322
  try:
274
- headers: Dict[str, str] = {"Content-Type": "application/json"}
275
- if self._envd_access_token:
276
- headers["X-Access-Token"] = self._envd_access_token
323
+ headers: Dict[str, str] = {
324
+ "Content-Type": "application/json",
325
+ "E2b-Sandbox-Id": self.sandbox_id,
326
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
327
+ }
277
328
  if self.traffic_access_token:
278
329
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
279
330
 
@@ -288,6 +339,9 @@ class Sandbox(BaseSandbox):
288
339
  raise err
289
340
  except httpx.TimeoutException:
290
341
  raise format_request_timeout_error()
342
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
343
+ self._handle_connection_error(err)
344
+ raise
291
345
 
292
346
  def list_code_contexts(self) -> List[Context]:
293
347
  """
@@ -296,9 +350,11 @@ class Sandbox(BaseSandbox):
296
350
  :return: List of contexts.
297
351
  """
298
352
  try:
299
- headers: Dict[str, str] = {"Content-Type": "application/json"}
300
- if self._envd_access_token:
301
- headers["X-Access-Token"] = self._envd_access_token
353
+ headers: Dict[str, str] = {
354
+ "Content-Type": "application/json",
355
+ "E2b-Sandbox-Id": self.sandbox_id,
356
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
357
+ }
302
358
  if self.traffic_access_token:
303
359
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
304
360
 
@@ -316,6 +372,9 @@ class Sandbox(BaseSandbox):
316
372
  return [Context.from_json(context_data) for context_data in data]
317
373
  except httpx.TimeoutException:
318
374
  raise format_request_timeout_error()
375
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
376
+ self._handle_connection_error(err)
377
+ raise
319
378
 
320
379
  def restart_code_context(
321
380
  self,
@@ -331,9 +390,11 @@ class Sandbox(BaseSandbox):
331
390
  context_id = context.id if isinstance(context, Context) else context
332
391
 
333
392
  try:
334
- headers: Dict[str, str] = {"Content-Type": "application/json"}
335
- if self._envd_access_token:
336
- headers["X-Access-Token"] = self._envd_access_token
393
+ headers: Dict[str, str] = {
394
+ "Content-Type": "application/json",
395
+ "E2b-Sandbox-Id": self.sandbox_id,
396
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
397
+ }
337
398
  if self.traffic_access_token:
338
399
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
339
400
 
@@ -348,3 +409,6 @@ class Sandbox(BaseSandbox):
348
409
  raise err
349
410
  except httpx.TimeoutException:
350
411
  raise format_request_timeout_error()
412
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
413
+ self._handle_connection_error(err)
414
+ raise
@@ -11,3 +11,10 @@ def format_execution_timeout_error() -> Exception:
11
11
  return TimeoutException(
12
12
  "Execution timed out — the 'timeout' option can be used to increase this timeout",
13
13
  )
14
+
15
+
16
+ def format_sandbox_killed_error() -> Exception:
17
+ return TimeoutException(
18
+ "The sandbox was killed while the request was in progress. This can happen when the sandbox times out or is killed manually. "
19
+ "You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout",
20
+ )
@@ -207,7 +207,7 @@ class Result:
207
207
 
208
208
  return formats
209
209
 
210
- def __str__(self) -> Optional[str]:
210
+ def __str__(self) -> str:
211
211
  """
212
212
  Returns the text representation of the data.
213
213
 
@@ -305,7 +305,12 @@ class Logs:
305
305
  stderr: List[str] = field(default_factory=list)
306
306
  """List of strings printed to stderr by prints, subprocesses, etc."""
307
307
 
308
- def __init__(self, stdout: List[str] = None, stderr: List[str] = None, **kwargs):
308
+ def __init__(
309
+ self,
310
+ stdout: Optional[List[str]] = None,
311
+ stderr: Optional[List[str]] = None,
312
+ **kwargs,
313
+ ):
309
314
  self.stdout = stdout or []
310
315
  self.stderr = stderr or []
311
316
 
@@ -329,7 +334,9 @@ def serialize_results(results: List[Result]) -> List[Dict[str, str]]:
329
334
  serialized_dict = {}
330
335
  for key in result.formats():
331
336
  if key == "chart":
332
- serialized_dict[key] = result.chart.to_dict()
337
+ chart = result.chart
338
+ if chart is not None:
339
+ serialized_dict[key] = chart.to_dict()
333
340
  else:
334
341
  serialized_dict[key] = result[key]
335
342
 
@@ -356,8 +363,8 @@ class Execution:
356
363
 
357
364
  def __init__(
358
365
  self,
359
- results: List[Result] = None,
360
- logs: Logs = None,
366
+ results: Optional[List[Result]] = None,
367
+ logs: Optional[Logs] = None,
361
368
  error: Optional[ExecutionError] = None,
362
369
  execution_count: Optional[int] = None,
363
370
  **kwargs,
@@ -420,7 +427,10 @@ def format_exception(res: Response):
420
427
  f"{res.text}: This error is likely due to sandbox timeout. You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout."
421
428
  )
422
429
  else:
423
- return SandboxException(f"{res.status_code}: {res.text}")
430
+ body = res.text.strip()
431
+ return SandboxException(
432
+ f"{res.status_code} {res.reason_phrase}{f': {body}' if body else ''}"
433
+ )
424
434
 
425
435
 
426
436
  def parse_output(
@@ -510,7 +520,7 @@ class Context:
510
520
  @classmethod
511
521
  def from_json(cls, data: Dict[str, str]):
512
522
  return cls(
513
- context_id=data.get("id"),
514
- language=data.get("language"),
515
- cwd=data.get("cwd"),
523
+ context_id=data["id"],
524
+ language=data["language"],
525
+ cwd=data["cwd"],
516
526
  )
@@ -0,0 +1,47 @@
1
+ [project]
2
+ name = "e2b-code-interpreter"
3
+ version = "2.11.0"
4
+ description = "E2B Code Interpreter - Stateful code execution"
5
+ authors = [{ name = "e2b", email = "hello@e2b.dev" }]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ requires-python = ">=3.11"
9
+ dependencies = [
10
+ "httpx>=0.20.0,<1.0.0",
11
+ "attrs>=21.3.0",
12
+ "e2b>=2.55.1,<3.0.0",
13
+ ]
14
+
15
+ [project.urls]
16
+ Homepage = "https://e2b.dev/?utm_source=pypi&utm_medium=referral&utm_campaign=package_homepage&utm_content=e2b-code-interpreter"
17
+ Repository = "https://github.com/e2b-dev/e2b/tree/main/packages/code-interpreter-python"
18
+ "Bug Tracker" = "https://github.com/e2b-dev/e2b/issues"
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "pytest>=9.0.3,<10",
23
+ "python-dotenv>=1.0.0,<2",
24
+ "pytest-dotenv>=0.5.2,<0.6",
25
+ "pytest-asyncio>=1.3.0,<2",
26
+ "pytest-xdist>=3.6.1,<4",
27
+ "matplotlib>=3.8.0,<4",
28
+ "ruff>=0.11.12,<0.12",
29
+ "ty>=0.0.15,<0.0.16",
30
+ ]
31
+
32
+ [build-system]
33
+ requires = ["uv_build>=0.10.0,<0.11.0"]
34
+ build-backend = "uv_build"
35
+
36
+ [tool.uv.build-backend]
37
+ module-name = ["e2b_code_interpreter"]
38
+ module-root = ""
39
+
40
+ [tool.uv.sources]
41
+ e2b = { workspace = true }
42
+
43
+ [tool.uv.workspace]
44
+ members = ["../python-sdk"]
45
+
46
+ [tool.ruff.lint]
47
+ ignore = ["F401", "F403"]
@@ -1,9 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 FOUNDRYLABS, INC.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
-
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,72 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: e2b-code-interpreter
3
- Version: 2.8.0
4
- Summary: E2B Code Interpreter - Stateful code execution
5
- Home-page: https://e2b.dev/
6
- License: MIT
7
- Author: e2b
8
- Author-email: hello@e2b.dev
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Requires-Dist: attrs (>=21.3.0)
17
- Requires-Dist: e2b (>=2.26.0,<3.0.0)
18
- Requires-Dist: httpx (>=0.20.0,<1.0.0)
19
- Project-URL: Bug Tracker, https://github.com/e2b-dev/code-interpreter/issues
20
- Project-URL: Repository, https://github.com/e2b-dev/code-interpreter/tree/main/python
21
- Description-Content-Type: text/markdown
22
-
23
- <p align="center">
24
- <img width="100" src="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-circle.png" alt="e2b logo">
25
- </p>
26
-
27
- <h4 align="center">
28
- <a href="https://pypi.org/project/e2b/">
29
- <img alt="Last 1 month downloads for the Python SDK" loading="lazy" width="200" height="20" decoding="async" data-nimg="1"
30
- style="color:transparent;width:auto;height:100%" src="https://img.shields.io/pypi/dm/e2b?label=PyPI%20Downloads">
31
- </a>
32
- </h4>
33
-
34
- <!---
35
- <img width="100%" src="/readme-assets/preview.png" alt="Cover image">
36
- --->
37
- ## What is E2B?
38
- [E2B](https://www.e2b.dev/) is an open-source infrastructure that allows you run to AI-generated code in secure isolated sandboxes in the cloud. To start and control sandboxes, use our [JavaScript SDK](https://www.npmjs.com/package/@e2b/code-interpreter) or [Python SDK](https://pypi.org/project/e2b_code_interpreter).
39
-
40
- ## Run your first Sandbox
41
-
42
- ### 1. Install SDK
43
-
44
- ```
45
- pip install e2b-code-interpreter
46
- ```
47
-
48
- ### 2. Get your E2B API key
49
- 1. Sign up to E2B [here](https://e2b.dev).
50
- 2. Get your API key [here](https://e2b.dev/dashboard?tab=keys).
51
- 3. Set environment variable with your API key.
52
- ```
53
- E2B_API_KEY=e2b_***
54
- ```
55
-
56
- ### 3. Execute code with code interpreter inside Sandbox
57
-
58
- ```py
59
- from e2b_code_interpreter import Sandbox
60
-
61
- with Sandbox.create() as sandbox:
62
- sandbox.run_code("x = 1")
63
- execution = sandbox.run_code("x+=1; x")
64
- print(execution.text) # outputs 2
65
- ```
66
-
67
- ### 4. Check docs
68
- Visit [E2B documentation](https://e2b.dev/docs).
69
-
70
- ### 5. E2B cookbook
71
- Visit our [Cookbook](https://github.com/e2b-dev/e2b-cookbook/tree/main) to get inspired by examples with different LLMs and AI frameworks.
72
-
@@ -1,49 +0,0 @@
1
- <p align="center">
2
- <img width="100" src="https://raw.githubusercontent.com/e2b-dev/E2B/refs/heads/main/readme-assets/logo-circle.png" alt="e2b logo">
3
- </p>
4
-
5
- <h4 align="center">
6
- <a href="https://pypi.org/project/e2b/">
7
- <img alt="Last 1 month downloads for the Python SDK" loading="lazy" width="200" height="20" decoding="async" data-nimg="1"
8
- style="color:transparent;width:auto;height:100%" src="https://img.shields.io/pypi/dm/e2b?label=PyPI%20Downloads">
9
- </a>
10
- </h4>
11
-
12
- <!---
13
- <img width="100%" src="/readme-assets/preview.png" alt="Cover image">
14
- --->
15
- ## What is E2B?
16
- [E2B](https://www.e2b.dev/) is an open-source infrastructure that allows you run to AI-generated code in secure isolated sandboxes in the cloud. To start and control sandboxes, use our [JavaScript SDK](https://www.npmjs.com/package/@e2b/code-interpreter) or [Python SDK](https://pypi.org/project/e2b_code_interpreter).
17
-
18
- ## Run your first Sandbox
19
-
20
- ### 1. Install SDK
21
-
22
- ```
23
- pip install e2b-code-interpreter
24
- ```
25
-
26
- ### 2. Get your E2B API key
27
- 1. Sign up to E2B [here](https://e2b.dev).
28
- 2. Get your API key [here](https://e2b.dev/dashboard?tab=keys).
29
- 3. Set environment variable with your API key.
30
- ```
31
- E2B_API_KEY=e2b_***
32
- ```
33
-
34
- ### 3. Execute code with code interpreter inside Sandbox
35
-
36
- ```py
37
- from e2b_code_interpreter import Sandbox
38
-
39
- with Sandbox.create() as sandbox:
40
- sandbox.run_code("x = 1")
41
- execution = sandbox.run_code("x+=1; x")
42
- print(execution.text) # outputs 2
43
- ```
44
-
45
- ### 4. Check docs
46
- Visit [E2B documentation](https://e2b.dev/docs).
47
-
48
- ### 5. E2B cookbook
49
- Visit our [Cookbook](https://github.com/e2b-dev/e2b-cookbook/tree/main) to get inspired by examples with different LLMs and AI frameworks.
@@ -1,37 +0,0 @@
1
- [tool.poetry]
2
- name = "e2b-code-interpreter"
3
- version = "2.8.0"
4
- description = "E2B Code Interpreter - Stateful code execution"
5
- authors = ["e2b <hello@e2b.dev>"]
6
- license = "MIT"
7
- readme = "README.md"
8
- homepage = "https://e2b.dev/"
9
- repository = "https://github.com/e2b-dev/code-interpreter/tree/main/python"
10
- packages = [{ include = "e2b_code_interpreter" }]
11
-
12
- [tool.poetry.dependencies]
13
- python = "^3.10"
14
-
15
- httpx = ">=0.20.0, <1.0.0"
16
- attrs = ">=21.3.0"
17
- e2b = "^2.26.0"
18
-
19
- [tool.poetry.group.dev.dependencies]
20
- pytest = "^9.0.3"
21
- python-dotenv = "^1.0.0"
22
- pytest-dotenv = "^0.5.2"
23
- pytest-asyncio = "^1.3.0"
24
- pytest-xdist = "^3.6.1"
25
- matplotlib = "^3.8.0"
26
- ruff = "^0.11.12"
27
-
28
-
29
- [build-system]
30
- requires = ["poetry-core"]
31
- build-backend = "poetry.core.masonry.api"
32
-
33
- [tool.poetry.urls]
34
- "Bug Tracker" = "https://github.com/e2b-dev/code-interpreter/issues"
35
-
36
- [tool.ruff.lint]
37
- ignore = ["F401", "F403"]