e2b-code-interpreter 0.0.7__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.7
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 = {"path": 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()
@@ -25,6 +25,7 @@ class CellExecution:
25
25
  """
26
26
 
27
27
  input_accepted: bool = False
28
+
28
29
  on_stdout: Optional[Callable[[ProcessMessage], Any]] = None
29
30
  on_stderr: Optional[Callable[[ProcessMessage], Any]] = None
30
31
  on_result: Optional[Callable[[Result], Any]] = None
@@ -44,8 +45,9 @@ class CellExecution:
44
45
 
45
46
  class JupyterKernelWebSocket:
46
47
 
47
- def __init__(self, url: str):
48
+ def __init__(self, url: str, session_id: str):
48
49
  self.url = url
50
+ self.session_id = session_id
49
51
  self._cells: Dict[str, CellExecution] = {}
50
52
  self._waiting_for_replies: Dict[str, DeferredFuture] = {}
51
53
  self._queue_in = Queue()
@@ -101,14 +103,13 @@ class JupyterKernelWebSocket:
101
103
 
102
104
  logger.debug("WebSocket started")
103
105
 
104
- @staticmethod
105
- def _get_execute_request(msg_id: str, code: str) -> str:
106
+ def _get_execute_request(self, msg_id: str, code: str) -> str:
106
107
  return json.dumps(
107
108
  {
108
109
  "header": {
109
110
  "msg_id": msg_id,
110
111
  "username": "e2b",
111
- "session": str(uuid.uuid4()),
112
+ "session": self.session_id,
112
113
  "msg_type": "execute_request",
113
114
  "version": "5.3",
114
115
  },
@@ -117,7 +118,7 @@ class JupyterKernelWebSocket:
117
118
  "content": {
118
119
  "code": code,
119
120
  "silent": False,
120
- "store_history": False,
121
+ "store_history": True,
121
122
  "user_expressions": {},
122
123
  "allow_stdin": False,
123
124
  },
@@ -237,6 +238,7 @@ class JupyterKernelWebSocket:
237
238
 
238
239
  elif data["msg_type"] == "execute_input":
239
240
  logger.debug(f"Input accepted for {parent_msg_ig}")
241
+ cell.partial_result.execution_count = data["content"]["execution_count"]
240
242
  cell.input_accepted = True
241
243
  else:
242
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.7"
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"