e2b-code-interpreter 2.8.0__tar.gz → 2.9.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: e2b-code-interpreter
3
- Version: 2.8.0
3
+ Version: 2.9.0
4
4
  Summary: E2B Code Interpreter - Stateful code execution
5
5
  Home-page: https://e2b.dev/
6
6
  License: MIT
@@ -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,6 +61,11 @@ class AsyncSandbox(BaseAsyncSandbox):
60
61
 
61
62
  @property
62
63
  def _jupyter_url(self) -> str:
64
+ # Honors the `sandbox_url` option and the `E2B_SANDBOX_URL` environment
65
+ # variable, same as the base SDK does for envd requests.
66
+ sandbox_url = self.connection_config._sandbox_url
67
+ if sandbox_url:
68
+ return sandbox_url
63
69
  return f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(JUPYTER_PORT)}"
64
70
 
65
71
  @property
@@ -83,6 +89,23 @@ class AsyncSandbox(BaseAsyncSandbox):
83
89
  transport=get_transport(self.connection_config, http2=False),
84
90
  )
85
91
 
92
+ async def _handle_connection_error(self, err: Exception) -> None:
93
+ """
94
+ Raises a descriptive exception if the connection error was caused by
95
+ the sandbox being killed mid-request. If the sandbox is still running
96
+ (or its state can't be determined), returns so the caller can re-raise
97
+ the original error.
98
+ """
99
+ try:
100
+ running = await self.is_running()
101
+ except Exception:
102
+ # The state check itself failed, so we can't tell whether the
103
+ # sandbox was killed — let the caller re-raise the original error
104
+ # instead of wrongly claiming the sandbox is gone.
105
+ return
106
+ if not running:
107
+ raise format_sandbox_killed_error() from err
108
+
86
109
  @overload
87
110
  async def run_code(
88
111
  self,
@@ -178,6 +201,8 @@ class AsyncSandbox(BaseAsyncSandbox):
178
201
  try:
179
202
  headers = {
180
203
  "Content-Type": "application/json",
204
+ "E2b-Sandbox-Id": self.sandbox_id,
205
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
181
206
  }
182
207
  if self._envd_access_token:
183
208
  headers["X-Access-Token"] = self._envd_access_token
@@ -217,6 +242,9 @@ class AsyncSandbox(BaseAsyncSandbox):
217
242
  raise format_execution_timeout_error()
218
243
  except httpx.TimeoutException:
219
244
  raise format_request_timeout_error()
245
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
246
+ await self._handle_connection_error(err)
247
+ raise
220
248
 
221
249
  async def create_code_context(
222
250
  self,
@@ -244,6 +272,8 @@ class AsyncSandbox(BaseAsyncSandbox):
244
272
  try:
245
273
  headers = {
246
274
  "Content-Type": "application/json",
275
+ "E2b-Sandbox-Id": self.sandbox_id,
276
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
247
277
  }
248
278
  if self.traffic_access_token:
249
279
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -263,6 +293,9 @@ class AsyncSandbox(BaseAsyncSandbox):
263
293
  return Context.from_json(data)
264
294
  except httpx.TimeoutException:
265
295
  raise format_request_timeout_error()
296
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
297
+ await self._handle_connection_error(err)
298
+ raise
266
299
 
267
300
  async def remove_code_context(
268
301
  self,
@@ -280,6 +313,8 @@ class AsyncSandbox(BaseAsyncSandbox):
280
313
  try:
281
314
  headers = {
282
315
  "Content-Type": "application/json",
316
+ "E2b-Sandbox-Id": self.sandbox_id,
317
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
283
318
  }
284
319
  if self.traffic_access_token:
285
320
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -295,6 +330,9 @@ class AsyncSandbox(BaseAsyncSandbox):
295
330
  raise err
296
331
  except httpx.TimeoutException:
297
332
  raise format_request_timeout_error()
333
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
334
+ await self._handle_connection_error(err)
335
+ raise
298
336
 
299
337
  async def list_code_contexts(self) -> List[Context]:
300
338
  """
@@ -305,6 +343,8 @@ class AsyncSandbox(BaseAsyncSandbox):
305
343
  try:
306
344
  headers = {
307
345
  "Content-Type": "application/json",
346
+ "E2b-Sandbox-Id": self.sandbox_id,
347
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
308
348
  }
309
349
  if self.traffic_access_token:
310
350
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -323,6 +363,9 @@ class AsyncSandbox(BaseAsyncSandbox):
323
363
  return [Context.from_json(context_data) for context_data in data]
324
364
  except httpx.TimeoutException:
325
365
  raise format_request_timeout_error()
366
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
367
+ await self._handle_connection_error(err)
368
+ raise
326
369
 
327
370
  async def restart_code_context(
328
371
  self,
@@ -339,6 +382,8 @@ class AsyncSandbox(BaseAsyncSandbox):
339
382
  try:
340
383
  headers = {
341
384
  "Content-Type": "application/json",
385
+ "E2b-Sandbox-Id": self.sandbox_id,
386
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
342
387
  }
343
388
  if self.traffic_access_token:
344
389
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
@@ -354,3 +399,6 @@ class AsyncSandbox(BaseAsyncSandbox):
354
399
  raise err
355
400
  except httpx.TimeoutException:
356
401
  raise format_request_timeout_error()
402
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
403
+ await self._handle_connection_error(err)
404
+ 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,6 +58,11 @@ class Sandbox(BaseSandbox):
57
58
 
58
59
  @property
59
60
  def _jupyter_url(self) -> str:
61
+ # Honors the `sandbox_url` option and the `E2B_SANDBOX_URL` environment
62
+ # variable, same as the base SDK does for envd requests.
63
+ sandbox_url = self.connection_config._sandbox_url
64
+ if sandbox_url:
65
+ return sandbox_url
60
66
  return f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(JUPYTER_PORT)}"
61
67
 
62
68
  @property
@@ -77,6 +83,23 @@ class Sandbox(BaseSandbox):
77
83
  # cancelled reliably.
78
84
  return Client(transport=get_transport(self.connection_config, http2=False))
79
85
 
86
+ def _handle_connection_error(self, err: Exception) -> None:
87
+ """
88
+ Raises a descriptive exception if the connection error was caused by
89
+ the sandbox being killed mid-request. If the sandbox is still running
90
+ (or its state can't be determined), returns so the caller can re-raise
91
+ the original error.
92
+ """
93
+ try:
94
+ running = self.is_running()
95
+ except Exception:
96
+ # The state check itself failed, so we can't tell whether the
97
+ # sandbox was killed — let the caller re-raise the original error
98
+ # instead of wrongly claiming the sandbox is gone.
99
+ return
100
+ if not running:
101
+ raise format_sandbox_killed_error() from err
102
+
80
103
  @overload
81
104
  def run_code(
82
105
  self,
@@ -171,7 +194,11 @@ class Sandbox(BaseSandbox):
171
194
  context_id = context.id if context else None
172
195
 
173
196
  try:
174
- headers: Dict[str, str] = {"Content-Type": "application/json"}
197
+ headers: Dict[str, str] = {
198
+ "Content-Type": "application/json",
199
+ "E2b-Sandbox-Id": self.sandbox_id,
200
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
201
+ }
175
202
  if self._envd_access_token:
176
203
  headers["X-Access-Token"] = self._envd_access_token
177
204
  if self.traffic_access_token:
@@ -210,6 +237,9 @@ class Sandbox(BaseSandbox):
210
237
  raise format_execution_timeout_error()
211
238
  except httpx.TimeoutException:
212
239
  raise format_request_timeout_error()
240
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
241
+ self._handle_connection_error(err)
242
+ raise
213
243
 
214
244
  def create_code_context(
215
245
  self,
@@ -235,9 +265,11 @@ class Sandbox(BaseSandbox):
235
265
  data["cwd"] = cwd
236
266
 
237
267
  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
268
+ headers: Dict[str, str] = {
269
+ "Content-Type": "application/json",
270
+ "E2b-Sandbox-Id": self.sandbox_id,
271
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
272
+ }
241
273
  if self.traffic_access_token:
242
274
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
243
275
 
@@ -256,6 +288,9 @@ class Sandbox(BaseSandbox):
256
288
  return Context.from_json(data)
257
289
  except httpx.TimeoutException:
258
290
  raise format_request_timeout_error()
291
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
292
+ self._handle_connection_error(err)
293
+ raise
259
294
 
260
295
  def remove_code_context(
261
296
  self,
@@ -271,9 +306,11 @@ class Sandbox(BaseSandbox):
271
306
  context_id = context.id if isinstance(context, Context) else context
272
307
 
273
308
  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
309
+ headers: Dict[str, str] = {
310
+ "Content-Type": "application/json",
311
+ "E2b-Sandbox-Id": self.sandbox_id,
312
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
313
+ }
277
314
  if self.traffic_access_token:
278
315
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
279
316
 
@@ -288,6 +325,9 @@ class Sandbox(BaseSandbox):
288
325
  raise err
289
326
  except httpx.TimeoutException:
290
327
  raise format_request_timeout_error()
328
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
329
+ self._handle_connection_error(err)
330
+ raise
291
331
 
292
332
  def list_code_contexts(self) -> List[Context]:
293
333
  """
@@ -296,9 +336,11 @@ class Sandbox(BaseSandbox):
296
336
  :return: List of contexts.
297
337
  """
298
338
  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
339
+ headers: Dict[str, str] = {
340
+ "Content-Type": "application/json",
341
+ "E2b-Sandbox-Id": self.sandbox_id,
342
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
343
+ }
302
344
  if self.traffic_access_token:
303
345
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
304
346
 
@@ -316,6 +358,9 @@ class Sandbox(BaseSandbox):
316
358
  return [Context.from_json(context_data) for context_data in data]
317
359
  except httpx.TimeoutException:
318
360
  raise format_request_timeout_error()
361
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
362
+ self._handle_connection_error(err)
363
+ raise
319
364
 
320
365
  def restart_code_context(
321
366
  self,
@@ -331,9 +376,11 @@ class Sandbox(BaseSandbox):
331
376
  context_id = context.id if isinstance(context, Context) else context
332
377
 
333
378
  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
379
+ headers: Dict[str, str] = {
380
+ "Content-Type": "application/json",
381
+ "E2b-Sandbox-Id": self.sandbox_id,
382
+ "E2b-Sandbox-Port": str(JUPYTER_PORT),
383
+ }
337
384
  if self.traffic_access_token:
338
385
  headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
339
386
 
@@ -348,3 +395,6 @@ class Sandbox(BaseSandbox):
348
395
  raise err
349
396
  except httpx.TimeoutException:
350
397
  raise format_request_timeout_error()
398
+ except (httpx.ReadError, httpx.RemoteProtocolError) as err:
399
+ self._handle_connection_error(err)
400
+ 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
+ )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "e2b-code-interpreter"
3
- version = "2.8.0"
3
+ version = "2.9.0"
4
4
  description = "E2B Code Interpreter - Stateful code execution"
5
5
  authors = ["e2b <hello@e2b.dev>"]
6
6
  license = "MIT"