e2b-code-interpreter 0.0.6__tar.gz → 0.0.8__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: 0.0.6
3
+ Version: 0.0.8
4
4
  Summary: E2B Code Interpreter - Stateful code execution
5
5
  Home-page: https://e2b.dev/
6
6
  License: Apache-2.0
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
- Requires-Dist: e2b (>=0.17.0)
17
+ Requires-Dist: e2b (>=0.17.1)
18
18
  Requires-Dist: pydantic (>1,<3)
19
19
  Requires-Dist: websocket-client (>=1.7.0,<2.0.0)
20
20
  Project-URL: Bug Tracker, https://github.com/e2b-dev/code-interpreter/issues
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  import threading
5
+ import uuid
6
+
5
7
  import requests
6
8
 
7
9
  from concurrent.futures import Future
@@ -91,16 +93,16 @@ class JupyterExtension:
91
93
  ws = ws_future.result(timeout=timeout)
92
94
  else:
93
95
  logger.debug(f"Creating new websocket connection to kernel {kernel_id}")
94
- ws = self._connect_to_kernel_ws(kernel_id, timeout=timeout)
96
+ ws = self._connect_to_kernel_ws(kernel_id, None, timeout=timeout)
95
97
 
96
- session_id = ws.send_execution_message(code, on_stdout, on_stderr, on_result)
98
+ message_id = ws.send_execution_message(code, on_stdout, on_stderr, on_result)
97
99
  logger.debug(
98
- f"Sent execution message to kernel {kernel_id}, session_id: {session_id}"
100
+ f"Sent execution message to kernel {kernel_id}, message_id: {message_id}"
99
101
  )
100
102
 
101
- result = ws.get_result(session_id, timeout=timeout)
103
+ result = ws.get_result(message_id, timeout=timeout)
102
104
  logger.debug(
103
- f"Received result from kernel {kernel_id}, session_id: {session_id}, result: {result}"
105
+ f"Received result from kernel {kernel_id}, message_id: {message_id}, result: {result}"
104
106
  )
105
107
 
106
108
  return result
@@ -139,24 +141,35 @@ class JupyterExtension:
139
141
  :param timeout: Timeout for the kernel creation request.
140
142
  :return: Kernel id of the created kernel
141
143
  """
142
- data = {"cwd": cwd}
143
- if kernel_name:
144
- data["kernel_name"] = kernel_name
144
+ kernel_name = kernel_name or "python3"
145
+
146
+ data = {"path": str(uuid.uuid4()), "kernel": {"name": kernel_name}, "type": "notebook", "name": str(uuid.uuid4())}
145
147
  logger.debug(f"Creating kernel with data: {data}")
146
148
 
147
149
  response = requests.post(
148
- f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels",
150
+ f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/sessions",
149
151
  json=data,
150
152
  timeout=timeout,
151
153
  )
152
154
  if not response.ok:
153
155
  raise KernelException(f"Failed to create kernel: {response.text}")
154
156
 
155
- kernel_id = response.json()["id"]
157
+ session_data = response.json()
158
+ session_id = session_data["id"]
159
+ kernel_id = session_data["kernel"]["id"]
160
+
161
+ response = requests.patch(
162
+ f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/sessions/{session_id}",
163
+ json={"path": cwd},
164
+ timeout=timeout,
165
+ )
166
+ if not response.ok:
167
+ raise KernelException(f"Failed to create kernel: {response.text}")
168
+
156
169
  logger.debug(f"Created kernel {kernel_id}")
157
170
 
158
171
  threading.Thread(
159
- target=self._connect_to_kernel_ws, args=(kernel_id, timeout)
172
+ target=self._connect_to_kernel_ws, args=(kernel_id, session_id, timeout)
160
173
  ).start()
161
174
  return kernel_id
162
175
 
@@ -186,7 +199,7 @@ class JupyterExtension:
186
199
  logger.debug(f"Restarted kernel {kernel_id}")
187
200
 
188
201
  threading.Thread(
189
- target=self._connect_to_kernel_ws, args=(kernel_id, timeout)
202
+ target=self._connect_to_kernel_ws, args=(kernel_id, None, timeout)
190
203
  ).start()
191
204
 
192
205
  def shutdown_kernel(
@@ -243,7 +256,7 @@ class JupyterExtension:
243
256
  ws.result().close()
244
257
 
245
258
  def _connect_to_kernel_ws(
246
- self, kernel_id: str, timeout: Optional[float] = TIMEOUT
259
+ self, kernel_id: str, session_id: Optional[str], timeout: Optional[float] = TIMEOUT
247
260
  ) -> JupyterKernelWebSocket:
248
261
  """
249
262
  Establishes a WebSocket connection to a specified Jupyter kernel.
@@ -257,9 +270,12 @@ class JupyterExtension:
257
270
  future = Future()
258
271
  self._connected_kernels[kernel_id] = future
259
272
 
273
+ session_id = session_id or str(uuid.uuid4())
260
274
  ws = JupyterKernelWebSocket(
261
275
  url=f"{self._sandbox.get_protocol('ws')}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}/channels",
276
+ session_id=session_id
262
277
  )
278
+
263
279
  ws.connect(timeout=timeout)
264
280
  logger.debug(f"Connected to kernel's ({kernel_id}) websocket.")
265
281
 
@@ -279,12 +295,14 @@ class JupyterExtension:
279
295
  kernel_id = self._sandbox.filesystem.read(
280
296
  "/root/.jupyter/kernel_id", timeout=timeout
281
297
  )
298
+
282
299
  if kernel_id is None and not self._sandbox.is_open:
283
300
  return
284
301
 
285
302
  kernel_id = kernel_id.strip()
303
+
286
304
  logger.debug(f"Default kernel id: {kernel_id}")
287
- self._connect_to_kernel_ws(kernel_id, timeout=timeout)
305
+ self._connect_to_kernel_ws(kernel_id, None, timeout=timeout)
288
306
  self._kernel_id_set.set_result(kernel_id)
289
307
 
290
308
  threading.Thread(target=setup_default_kernel).start()
@@ -5,14 +5,13 @@ import time
5
5
  import uuid
6
6
  from concurrent.futures import Future
7
7
  from queue import Queue
8
- from typing import Callable, Dict, List, Any, Optional
8
+ from typing import Callable, Dict, Any, Optional
9
9
 
10
10
  from e2b import ProcessMessage
11
11
  from e2b.constants import TIMEOUT
12
12
  from e2b.sandbox import TimeoutException
13
13
  from e2b.sandbox.websocket_client import WebSocket
14
14
  from e2b.utils.future import DeferredFuture
15
- from pydantic import ConfigDict, PrivateAttr, BaseModel
16
15
 
17
16
  from e2b_code_interpreter.models import Execution, Result, Error
18
17
 
@@ -26,6 +25,7 @@ class CellExecution:
26
25
  """
27
26
 
28
27
  input_accepted: bool = False
28
+
29
29
  on_stdout: Optional[Callable[[ProcessMessage], Any]] = None
30
30
  on_stderr: Optional[Callable[[ProcessMessage], Any]] = None
31
31
  on_result: Optional[Callable[[Result], Any]] = None
@@ -45,8 +45,9 @@ class CellExecution:
45
45
 
46
46
  class JupyterKernelWebSocket:
47
47
 
48
- def __init__(self, url: str):
48
+ def __init__(self, url: str, session_id: str):
49
49
  self.url = url
50
+ self.session_id = session_id
50
51
  self._cells: Dict[str, CellExecution] = {}
51
52
  self._waiting_for_replies: Dict[str, DeferredFuture] = {}
52
53
  self._queue_in = Queue()
@@ -102,14 +103,13 @@ class JupyterKernelWebSocket:
102
103
 
103
104
  logger.debug("WebSocket started")
104
105
 
105
- @staticmethod
106
- def _get_execute_request(msg_id: str, code: str) -> str:
106
+ def _get_execute_request(self, msg_id: str, code: str) -> str:
107
107
  return json.dumps(
108
108
  {
109
109
  "header": {
110
110
  "msg_id": msg_id,
111
111
  "username": "e2b",
112
- "session": str(uuid.uuid4()),
112
+ "session": self.session_id,
113
113
  "msg_type": "execute_request",
114
114
  "version": "5.3",
115
115
  },
@@ -118,7 +118,7 @@ class JupyterKernelWebSocket:
118
118
  "content": {
119
119
  "code": code,
120
120
  "silent": False,
121
- "store_history": False,
121
+ "store_history": True,
122
122
  "user_expressions": {},
123
123
  "allow_stdin": False,
124
124
  },
@@ -238,6 +238,7 @@ class JupyterKernelWebSocket:
238
238
 
239
239
  elif data["msg_type"] == "execute_input":
240
240
  logger.debug(f"Input accepted for {parent_msg_ig}")
241
+ cell.partial_result.execution_count = data["content"]["execution_count"]
241
242
  cell.input_accepted = True
242
243
  else:
243
244
  logger.warning(f"[UNHANDLED MESSAGE TYPE]: {data['msg_type']}")
@@ -124,6 +124,9 @@ class Result:
124
124
  """
125
125
  return self.text
126
126
 
127
+ def __repr__(self) -> str:
128
+ return f"Result({self.text})"
129
+
127
130
  def _repr_html_(self) -> Optional[str]:
128
131
  """
129
132
  Returns the HTML representation of the data.
@@ -222,6 +225,8 @@ class Execution(BaseModel):
222
225
  "Logs printed to stdout and stderr during execution."
223
226
  error: Optional[Error] = None
224
227
  "Error object if an error occurred, None otherwise."
228
+ execution_count: Optional[int] = None
229
+ "Execution count of the cell."
225
230
 
226
231
  @property
227
232
  def text(self) -> Optional[str]:
@@ -249,7 +254,7 @@ class Execution(BaseModel):
249
254
  serialized = []
250
255
  for result in results:
251
256
  serialized_dict = {key: result[key] for key in result.formats()}
252
- serialized_dict['text'] = result.text
257
+ serialized_dict["text"] = result.text
253
258
  serialized.append(serialized_dict)
254
259
  return serialized
255
260
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "e2b-code-interpreter"
3
- version = "0.0.6"
3
+ version = "0.0.8"
4
4
  description = "E2B Code Interpreter - Stateful code execution"
5
5
  authors = ["e2b <hello@e2b.dev>"]
6
6
  license = "Apache-2.0"
@@ -14,7 +14,7 @@ python = "^3.8"
14
14
 
15
15
  pydantic = ">1, <3"
16
16
  websocket-client = "^1.7.0"
17
- e2b = ">=0.17.0"
17
+ e2b = ">=0.17.1"
18
18
 
19
19
  [tool.poetry.group.dev.dependencies]
20
20
  black = "^24.3.0"