scalebox-sdk 0.1.0__py3-none-any.whl

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.
Files changed (157) hide show
  1. scalebox/__init__.py +80 -0
  2. scalebox/api/__init__.py +128 -0
  3. scalebox/api/client/__init__.py +8 -0
  4. scalebox/api/client/api/__init__.py +1 -0
  5. scalebox/api/client/api/sandboxes/__init__.py +0 -0
  6. scalebox/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
  7. scalebox/api/client/api/sandboxes/get_sandboxes.py +176 -0
  8. scalebox/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
  9. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
  10. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
  11. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +214 -0
  12. scalebox/api/client/api/sandboxes/get_v2_sandboxes.py +229 -0
  13. scalebox/api/client/api/sandboxes/post_sandboxes.py +174 -0
  14. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
  15. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +182 -0
  16. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +190 -0
  17. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +194 -0
  18. scalebox/api/client/client.py +288 -0
  19. scalebox/api/client/errors.py +16 -0
  20. scalebox/api/client/models/__init__.py +81 -0
  21. scalebox/api/client/models/build_log_entry.py +79 -0
  22. scalebox/api/client/models/created_access_token.py +100 -0
  23. scalebox/api/client/models/created_team_api_key.py +166 -0
  24. scalebox/api/client/models/error.py +67 -0
  25. scalebox/api/client/models/identifier_masking_details.py +83 -0
  26. scalebox/api/client/models/listed_sandbox.py +138 -0
  27. scalebox/api/client/models/log_level.py +11 -0
  28. scalebox/api/client/models/new_access_token.py +59 -0
  29. scalebox/api/client/models/new_sandbox.py +125 -0
  30. scalebox/api/client/models/new_team_api_key.py +59 -0
  31. scalebox/api/client/models/node.py +154 -0
  32. scalebox/api/client/models/node_detail.py +152 -0
  33. scalebox/api/client/models/node_status.py +11 -0
  34. scalebox/api/client/models/node_status_change.py +61 -0
  35. scalebox/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
  36. scalebox/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
  37. scalebox/api/client/models/resumed_sandbox.py +68 -0
  38. scalebox/api/client/models/sandbox.py +125 -0
  39. scalebox/api/client/models/sandbox_detail.py +178 -0
  40. scalebox/api/client/models/sandbox_log.py +70 -0
  41. scalebox/api/client/models/sandbox_logs.py +73 -0
  42. scalebox/api/client/models/sandbox_metric.py +110 -0
  43. scalebox/api/client/models/sandbox_state.py +9 -0
  44. scalebox/api/client/models/sandboxes_with_metrics.py +59 -0
  45. scalebox/api/client/models/team.py +83 -0
  46. scalebox/api/client/models/team_api_key.py +158 -0
  47. scalebox/api/client/models/team_user.py +68 -0
  48. scalebox/api/client/models/template.py +179 -0
  49. scalebox/api/client/models/template_build.py +117 -0
  50. scalebox/api/client/models/template_build_file_upload.py +70 -0
  51. scalebox/api/client/models/template_build_request.py +115 -0
  52. scalebox/api/client/models/template_build_request_v2.py +88 -0
  53. scalebox/api/client/models/template_build_start_v2.py +114 -0
  54. scalebox/api/client/models/template_build_status.py +11 -0
  55. scalebox/api/client/models/template_step.py +91 -0
  56. scalebox/api/client/models/template_update_request.py +59 -0
  57. scalebox/api/client/models/update_team_api_key.py +59 -0
  58. scalebox/api/client/py.typed +1 -0
  59. scalebox/api/client/types.py +46 -0
  60. scalebox/api/metadata.py +19 -0
  61. scalebox/cli.py +125 -0
  62. scalebox/client/__init__.py +0 -0
  63. scalebox/client/aclient.py +57 -0
  64. scalebox/client/api.proto +460 -0
  65. scalebox/client/buf.gen.yaml +8 -0
  66. scalebox/client/client.py +102 -0
  67. scalebox/client/requirements.txt +5 -0
  68. scalebox/code_interpreter/__init__.py +12 -0
  69. scalebox/code_interpreter/charts.py +230 -0
  70. scalebox/code_interpreter/code_interpreter_async.py +369 -0
  71. scalebox/code_interpreter/code_interpreter_sync.py +317 -0
  72. scalebox/code_interpreter/constants.py +3 -0
  73. scalebox/code_interpreter/exceptions.py +13 -0
  74. scalebox/code_interpreter/models.py +485 -0
  75. scalebox/connection_config.py +92 -0
  76. scalebox/csx_connect/__init__.py +1 -0
  77. scalebox/csx_connect/client.py +485 -0
  78. scalebox/csx_desktop/__init__.py +0 -0
  79. scalebox/csx_desktop/main.py +651 -0
  80. scalebox/exceptions.py +83 -0
  81. scalebox/generated/__init__.py +0 -0
  82. scalebox/generated/api.py +61 -0
  83. scalebox/generated/api_pb2.py +203 -0
  84. scalebox/generated/api_pb2.pyi +956 -0
  85. scalebox/generated/api_pb2_connect.py +1456 -0
  86. scalebox/generated/rpc.py +50 -0
  87. scalebox/generated/versions.py +3 -0
  88. scalebox/requirements.txt +36 -0
  89. scalebox/sandbox/__init__.py +0 -0
  90. scalebox/sandbox/commands/__init__.py +0 -0
  91. scalebox/sandbox/commands/command_handle.py +69 -0
  92. scalebox/sandbox/commands/main.py +39 -0
  93. scalebox/sandbox/filesystem/__init__.py +0 -0
  94. scalebox/sandbox/filesystem/filesystem.py +95 -0
  95. scalebox/sandbox/filesystem/watch_handle.py +60 -0
  96. scalebox/sandbox/main.py +139 -0
  97. scalebox/sandbox/sandbox_api.py +91 -0
  98. scalebox/sandbox/signature.py +40 -0
  99. scalebox/sandbox/utils.py +34 -0
  100. scalebox/sandbox_async/__init__.py +1 -0
  101. scalebox/sandbox_async/commands/command.py +307 -0
  102. scalebox/sandbox_async/commands/command_handle.py +187 -0
  103. scalebox/sandbox_async/commands/pty.py +187 -0
  104. scalebox/sandbox_async/filesystem/filesystem.py +557 -0
  105. scalebox/sandbox_async/filesystem/watch_handle.py +61 -0
  106. scalebox/sandbox_async/main.py +646 -0
  107. scalebox/sandbox_async/sandbox_api.py +365 -0
  108. scalebox/sandbox_async/utils.py +7 -0
  109. scalebox/sandbox_sync/__init__.py +2 -0
  110. scalebox/sandbox_sync/commands/__init__.py +0 -0
  111. scalebox/sandbox_sync/commands/command.py +300 -0
  112. scalebox/sandbox_sync/commands/command_handle.py +150 -0
  113. scalebox/sandbox_sync/commands/pty.py +181 -0
  114. scalebox/sandbox_sync/filesystem/__init__.py +0 -0
  115. scalebox/sandbox_sync/filesystem/filesystem.py +543 -0
  116. scalebox/sandbox_sync/filesystem/watch_handle.py +66 -0
  117. scalebox/sandbox_sync/main.py +790 -0
  118. scalebox/sandbox_sync/sandbox_api.py +356 -0
  119. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -0
  120. scalebox/test/README.md +329 -0
  121. scalebox/test/__init__.py +0 -0
  122. scalebox/test/aclient.py +72 -0
  123. scalebox/test/code_interpreter_centext.py +21 -0
  124. scalebox/test/code_interpreter_centext_sync.py +21 -0
  125. scalebox/test/code_interpreter_test.py +34 -0
  126. scalebox/test/code_interpreter_test_sync.py +34 -0
  127. scalebox/test/run_all_validation_tests.py +334 -0
  128. scalebox/test/run_code_interpreter_tests.sh +67 -0
  129. scalebox/test/run_tests.sh +230 -0
  130. scalebox/test/test_basic.py +78 -0
  131. scalebox/test/test_code_interpreter_async_comprehensive.py +2653 -0
  132. scalebox/test/test_code_interpreter_e2basync_comprehensive.py +2655 -0
  133. scalebox/test/test_code_interpreter_e2bsync_comprehensive.py +3416 -0
  134. scalebox/test/test_code_interpreter_sync_comprehensive.py +3412 -0
  135. scalebox/test/test_e2b_first.py +11 -0
  136. scalebox/test/test_sandbox_async_comprehensive.py +738 -0
  137. scalebox/test/test_sandbox_stress_and_edge_cases.py +778 -0
  138. scalebox/test/test_sandbox_sync_comprehensive.py +770 -0
  139. scalebox/test/test_sandbox_usage_examples.py +987 -0
  140. scalebox/test/testacreate.py +24 -0
  141. scalebox/test/testagetinfo.py +18 -0
  142. scalebox/test/testcodeinterpreter_async.py +508 -0
  143. scalebox/test/testcodeinterpreter_sync.py +239 -0
  144. scalebox/test/testcomputeuse.py +243 -0
  145. scalebox/test/testnovnc.py +12 -0
  146. scalebox/test/testsandbox_async.py +118 -0
  147. scalebox/test/testsandbox_sync.py +38 -0
  148. scalebox/utils/__init__.py +0 -0
  149. scalebox/utils/httpcoreclient.py +297 -0
  150. scalebox/utils/httpxclient.py +403 -0
  151. scalebox/version.py +16 -0
  152. scalebox_sdk-0.1.0.dist-info/METADATA +292 -0
  153. scalebox_sdk-0.1.0.dist-info/RECORD +157 -0
  154. scalebox_sdk-0.1.0.dist-info/WHEEL +5 -0
  155. scalebox_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  156. scalebox_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
  157. scalebox_sdk-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,369 @@
1
+ import logging
2
+ from typing import Dict, Literal, Optional, Union, overload
3
+
4
+ import aiohttp
5
+ import httpx
6
+ from aiohttp import TCPConnector
7
+ from httpx import AsyncClient
8
+
9
+ from ..connection_config import ConnectionConfig
10
+ from ..exceptions import InvalidArgumentException
11
+ from ..generated import api_pb2, api_pb2_connect
12
+ from ..sandbox_async.main import AsyncSandbox as BaseAsyncSandbox
13
+ from .constants import DEFAULT_TEMPLATE, DEFAULT_TIMEOUT, JUPYTER_PORT
14
+ from .exceptions import format_execution_timeout_error, format_request_timeout_error
15
+ from .models import (
16
+ Context,
17
+ Execution,
18
+ ExecutionError,
19
+ OutputHandler,
20
+ OutputMessage,
21
+ Result,
22
+ aextract_exception,
23
+ parse_output,
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class AsyncSandbox(BaseAsyncSandbox):
30
+ """
31
+ E2B cloud sandbox is a secure and isolated cloud environment.
32
+
33
+ The sandbox allows you to:
34
+ - Access Linux OS
35
+ - Create, list, and delete files and directories
36
+ - Run commands
37
+ - Run isolated code
38
+ - Access the internet
39
+
40
+ Check docs [here](https://e2b.dev/docs).
41
+
42
+ Use the `AsyncSandbox.create()` to create a new sandbox.
43
+
44
+ Example:
45
+ ```python
46
+ from e2b_code_interpreter import AsyncSandbox
47
+ sandbox = await AsyncSandbox.create()
48
+ ```
49
+ """
50
+
51
+ default_template = DEFAULT_TEMPLATE
52
+
53
+ @property
54
+ def _jupyter_url(self) -> str:
55
+ return f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(JUPYTER_PORT)}"
56
+
57
+ @property
58
+ def _client(self) -> AsyncClient:
59
+ return AsyncClient(transport=self._transport)
60
+
61
+ @overload
62
+ async def run_code(
63
+ self,
64
+ code: str,
65
+ language: Union[Literal["python"], None] = None,
66
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
67
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
68
+ on_result: Optional[OutputHandler[Result]] = None,
69
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
70
+ envs: Optional[Dict[str, str]] = None,
71
+ timeout: Optional[float] = None,
72
+ request_timeout: Optional[float] = None,
73
+ ) -> Execution:
74
+ """
75
+ Runs the code as Python.
76
+
77
+ Specify the `language` or `context` option to run the code as a different language or in a different `Context`.
78
+
79
+ You can reference previously defined variables, imports, and functions in the code.
80
+
81
+ :param code: Code to execute
82
+ :param language: Language to use for code execution. If not defined, the default Python context is used.
83
+ :param on_stdout: Callback for stdout messages
84
+ :param on_stderr: Callback for stderr messages
85
+ :param on_result: Callback for the `Result` object
86
+ :param on_error: Callback for the `ExecutionError` object
87
+ :param envs: Custom environment variables
88
+ :param timeout: Timeout for the code execution in **seconds**
89
+ :param request_timeout: Timeout for the request in **seconds**
90
+
91
+ :return: `Execution` result object
92
+ """
93
+ ...
94
+
95
+ @overload
96
+ async def run_code(
97
+ self,
98
+ code: str,
99
+ language: Optional[str] = "python",
100
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
101
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
102
+ on_result: Optional[OutputHandler[Result]] = None,
103
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
104
+ envs: Optional[Dict[str, str]] = None,
105
+ timeout: Optional[float] = None,
106
+ request_timeout: Optional[float] = None,
107
+ ) -> Execution:
108
+ """
109
+ Runs the code for the specified language.
110
+
111
+ Specify the `language` or `context` option to run the code as a different language or in a different `Context`.
112
+ If no language is specified, Python is used.
113
+
114
+ You can reference previously defined variables, imports, and functions in the code.
115
+
116
+ :param code: Code to execute
117
+ :param language: Language to use for code execution. If not defined, the default Python context is used.
118
+ :param on_stdout: Callback for stdout messages
119
+ :param on_stderr: Callback for stderr messages
120
+ :param on_result: Callback for the `Result` object
121
+ :param on_error: Callback for the `ExecutionError` object
122
+ :param envs: Custom environment variables
123
+ :param timeout: Timeout for the code execution in **seconds**
124
+ :param request_timeout: Timeout for the request in **seconds**
125
+
126
+ :return: `Execution` result object
127
+ """
128
+ ...
129
+
130
+ @overload
131
+ async def run_code(
132
+ self,
133
+ code: str,
134
+ context: Optional[Context] = None,
135
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
136
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
137
+ on_result: Optional[OutputHandler[Result]] = None,
138
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
139
+ envs: Optional[Dict[str, str]] = None,
140
+ timeout: Optional[float] = None,
141
+ request_timeout: Optional[float] = None,
142
+ ) -> Execution:
143
+ """
144
+ Runs the code in the specified context, if not specified, the default context is used.
145
+
146
+ Specify the `language` or `context` option to run the code as a different language or in a different `Context`.
147
+
148
+ You can reference previously defined variables, imports, and functions in the code.
149
+
150
+ :param code: Code to execute
151
+ :param context: Concrete context to run the code in. If not specified, the default context for the language is used. It's mutually exclusive with the language.
152
+ :param on_stdout: Callback for stdout messages
153
+ :param on_stderr: Callback for stderr messages
154
+ :param on_result: Callback for the `Result` object
155
+ :param on_error: Callback for the `ExecutionError` object
156
+ :param envs: Custom environment variables
157
+ :param timeout: Timeout for the code execution in **seconds**
158
+ :param request_timeout: Timeout for the request in **seconds**
159
+
160
+ :return: `Execution` result object
161
+ """
162
+ ...
163
+
164
+ async def run_code(
165
+ self,
166
+ code: str,
167
+ language: Optional[str] = None,
168
+ context: Optional[Context] = None,
169
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
170
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
171
+ on_result: Optional[OutputHandler[Result]] = None,
172
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
173
+ envs: Optional[Dict[str, str]] = None,
174
+ timeout: Optional[float] = None,
175
+ request_timeout: Optional[float] = None,
176
+ ) -> Execution:
177
+ logger.debug(f"Executing code {code}")
178
+
179
+ """
180
+ Execute code in the sandbox and return the execution result.
181
+ """
182
+ logger.debug(f"Executing code: {code}")
183
+
184
+ if context and language:
185
+ raise Exception(
186
+ "You can provide context or language, but not both at the same time."
187
+ )
188
+
189
+ # timeout = None if timeout == 0 else (timeout or 30)
190
+ request_timeout = request_timeout or self._connection_config.request_timeout
191
+ context_id = context.id if context else None
192
+
193
+ # Ensure session exists
194
+ if not self._session or self._session.closed:
195
+ connector = aiohttp.TCPConnector(
196
+ limit=100,
197
+ limit_per_host=20,
198
+ keepalive_timeout=30,
199
+ enable_cleanup_closed=True,
200
+ ssl=False,
201
+ )
202
+ self._session = aiohttp.ClientSession(
203
+ connector=connector,
204
+ timeout=aiohttp.ClientTimeout(total=request_timeout),
205
+ )
206
+
207
+ # Set headers
208
+ headers = {
209
+ "Authorization": "Bearer root",
210
+ }
211
+
212
+ try:
213
+ # Create client and execute request
214
+ client = api_pb2_connect.AsyncExecutionServiceClient(
215
+ http_client=self._session,
216
+ base_url=self.envd_api_url,
217
+ )
218
+
219
+ # Build request
220
+ request = api_pb2.ExecuteRequest(
221
+ code=code,
222
+ language=language or "",
223
+ context_id=context_id or "",
224
+ env_vars=envs or {},
225
+ )
226
+
227
+ # Execute request and get response stream
228
+ responses = client.execute(
229
+ req=request,
230
+ extra_headers=headers,
231
+ timeout_seconds=request_timeout,
232
+ )
233
+
234
+ execution = Execution()
235
+
236
+ # Process response stream
237
+ async for response in responses:
238
+ parse_output(
239
+ execution,
240
+ response,
241
+ on_stdout=on_stdout,
242
+ on_stderr=on_stderr,
243
+ on_result=on_result,
244
+ on_error=on_error,
245
+ )
246
+
247
+ return execution
248
+
249
+ except Exception as e:
250
+ # Handle exception
251
+ logger.error(f"Error executing code: {e}")
252
+ raise
253
+ finally:
254
+ # Don't close session here, let context manager handle it
255
+ pass
256
+
257
+ async def create_code_context(
258
+ self,
259
+ cwd: Optional[str] = None,
260
+ language: Optional[str] = None,
261
+ request_timeout: Optional[float] = None,
262
+ ) -> Context:
263
+ """
264
+ Creates a new context to run code in.
265
+
266
+ :param cwd: Set the current working directory for the context, defaults to `/home/user`
267
+ :param language: Language of the context. If not specified, defaults to Python
268
+ :param request_timeout: Timeout for the request in **milliseconds**
269
+
270
+ :return: Context object
271
+ """
272
+ logger.debug(f"Creating new {language} context")
273
+
274
+ request_timeout = request_timeout or self._connection_config.request_timeout
275
+
276
+ # Ensure session exists
277
+ if not self._session or self._session.closed:
278
+ connector = aiohttp.TCPConnector(
279
+ limit=100,
280
+ limit_per_host=20,
281
+ keepalive_timeout=30,
282
+ enable_cleanup_closed=True,
283
+ ssl=False,
284
+ )
285
+ self._session = aiohttp.ClientSession(
286
+ connector=connector,
287
+ timeout=aiohttp.ClientTimeout(total=request_timeout),
288
+ )
289
+
290
+ data = {}
291
+ if language:
292
+ data["language"] = language
293
+ if cwd:
294
+ data["cwd"] = cwd
295
+
296
+ try:
297
+ client = api_pb2_connect.AsyncContextServiceClient(
298
+ http_client=self._session,
299
+ base_url=self.envd_api_url,
300
+ )
301
+ headers = {
302
+ "Authorization": "Bearer root",
303
+ }
304
+
305
+ # Build request
306
+ request = api_pb2.CreateContextRequest(
307
+ language=language or "",
308
+ cwd=cwd or "",
309
+ )
310
+
311
+ # Execute request and get response stream
312
+ response = await client.create_context(
313
+ req=request,
314
+ extra_headers=headers,
315
+ )
316
+ return Context.from_json(
317
+ {
318
+ "id": response.id,
319
+ "language": response.language,
320
+ "cwd": response.cwd,
321
+ }
322
+ )
323
+ except Exception as e:
324
+ logger.error(f"Error create_code_context: {e}")
325
+ raise e
326
+ finally:
327
+ # Don't close session here, let context manager handle it
328
+ pass
329
+
330
+ async def destroy_context(self, context: Context) -> None:
331
+ """
332
+ Destroys a context.
333
+
334
+ :param context: Context to destroy
335
+ """
336
+ logger.debug(f"Destroying context {context.id}")
337
+
338
+ request_timeout = self._connection_config.request_timeout
339
+
340
+ # Ensure session exists
341
+ if not self._session or self._session.closed:
342
+ connector = aiohttp.TCPConnector(
343
+ limit=100,
344
+ limit_per_host=20,
345
+ keepalive_timeout=30,
346
+ enable_cleanup_closed=True,
347
+ ssl=False,
348
+ )
349
+ self._session = aiohttp.ClientSession(
350
+ connector=connector,
351
+ timeout=aiohttp.ClientTimeout(total=request_timeout),
352
+ )
353
+
354
+ # Create destroy context request
355
+ destroy_context_request = api_pb2.DestroyContextRequest(
356
+ context_id=context.id,
357
+ )
358
+
359
+ try:
360
+ client = api_pb2_connect.AsyncContextServiceClient(
361
+ base_url=self.envd_api_url,
362
+ http_client=self._session,
363
+ )
364
+ headers = {
365
+ "Authorization": "Bearer root",
366
+ }
367
+ await client.destroy_context(destroy_context_request, extra_headers=headers)
368
+ except Exception as e:
369
+ logger.warning(f"Failed to destroy context {context.id}: {e}")
@@ -0,0 +1,317 @@
1
+ import logging
2
+ import socket
3
+ import time
4
+ from typing import Dict, Iterator, Literal, Optional, Union, overload
5
+
6
+ import urllib3
7
+ from httpx import Timeout
8
+ from urllib3 import Retry
9
+
10
+ from ..exceptions import InvalidArgumentException
11
+ from ..generated import api_pb2, api_pb2_connect
12
+ from ..sandbox_sync.main import Sandbox as BaseSandbox
13
+ from .constants import DEFAULT_TEMPLATE, DEFAULT_TIMEOUT, JUPYTER_PORT
14
+ from .exceptions import format_execution_timeout_error, format_request_timeout_error
15
+ from .models import (
16
+ Context,
17
+ Execution,
18
+ ExecutionError,
19
+ OutputHandler,
20
+ OutputMessage,
21
+ Result,
22
+ parse_output,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class Sandbox(BaseSandbox):
29
+ """
30
+ E2B cloud sandbox is a secure and isolated cloud environment.
31
+
32
+ The sandbox allows you to:
33
+ - Access Linux OS
34
+ - Create, list, and delete files and directories
35
+ - Run commands
36
+ - Run isolated code
37
+ - Access the internet
38
+
39
+ Check docs [here](https://.dev/docs).
40
+
41
+ Use the `Sandbox()` to create a new sandbox.
42
+
43
+ Example:
44
+ ```python
45
+ from scalebox.code_interpreter import Sandbox
46
+
47
+ sandbox = Sandbox()
48
+ ```
49
+ """
50
+
51
+ default_template = DEFAULT_TEMPLATE
52
+
53
+ @property
54
+ def _jupyter_url(self) -> str:
55
+ return f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(JUPYTER_PORT)}"
56
+
57
+ @overload
58
+ def run_code(
59
+ self,
60
+ code: str,
61
+ language: Union[Literal["python"], None] = "python",
62
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
63
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
64
+ on_result: Optional[OutputHandler[Result]] = None,
65
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
66
+ envs: Optional[Dict[str, str]] = None,
67
+ timeout: Optional[float] = None,
68
+ request_timeout: Optional[float] = None,
69
+ ) -> Execution:
70
+ """
71
+ Runs the code as Python.
72
+
73
+ Specify the `language` or `context` option to run the code as a different language or in a different `Context`.
74
+
75
+ You can reference previously defined variables, imports, and functions in the code.
76
+
77
+ :param code: Code to execute
78
+ :param language: Language to use for code execution. If not defined, the default Python context is used.
79
+ :param on_stdout: Callback for stdout messages
80
+ :param on_stderr: Callback for stderr messages
81
+ :param on_result: Callback for the `Result` object
82
+ :param on_error: Callback for the `ExecutionError` object
83
+ :param envs: Custom environment variables
84
+ :param timeout: Timeout for the code execution in **seconds**
85
+ :param request_timeout: Timeout for the request in **seconds**
86
+
87
+ :return: `Execution` result object
88
+ """
89
+ ...
90
+
91
+ @overload
92
+ def run_code(
93
+ self,
94
+ code: str,
95
+ language: Optional[str] = "python",
96
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
97
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
98
+ on_result: Optional[OutputHandler[Result]] = None,
99
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
100
+ envs: Optional[Dict[str, str]] = None,
101
+ timeout: Optional[float] = None,
102
+ request_timeout: Optional[float] = None,
103
+ ) -> Execution:
104
+ """
105
+ Runs the code for the specified language.
106
+
107
+ Specify the `language` or `context` option to run the code as a different language or in a different `Context`.
108
+ If no language is specified, Python is used.
109
+
110
+ You can reference previously defined variables, imports, and functions in the code.
111
+
112
+ :param code: Code to execute
113
+ :param language: Language to use for code execution. If not defined, the default Python context is used.
114
+ :param on_stdout: Callback for stdout messages
115
+ :param on_stderr: Callback for stderr messages
116
+ :param on_result: Callback for the `Result` object
117
+ :param on_error: Callback for the `ExecutionError` object
118
+ :param envs: Custom environment variables
119
+ :param timeout: Timeout for the code execution in **seconds**
120
+ :param request_timeout: Timeout for the request in **seconds**
121
+
122
+ :return: `Execution` result object
123
+ """
124
+ ...
125
+
126
+ @overload
127
+ def run_code(
128
+ self,
129
+ code: str,
130
+ context: Optional[Context] = None,
131
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
132
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
133
+ on_result: Optional[OutputHandler[Result]] = None,
134
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
135
+ envs: Optional[Dict[str, str]] = None,
136
+ timeout: Optional[float] = None,
137
+ request_timeout: Optional[float] = None,
138
+ ) -> Execution:
139
+ """
140
+ Runs the code in the specified context, if not specified, the default context is used.
141
+
142
+ Specify the `language` or `context` option to run the code as a different language or in a different `Context`.
143
+
144
+ You can reference previously defined variables, imports, and functions in the code.
145
+
146
+ :param code: Code to execute
147
+ :param context: Concrete context to run the code in. If not specified, the default context for the language is used. It's mutually exclusive with the language.
148
+ :param on_stdout: Callback for stdout messages
149
+ :param on_stderr: Callback for stderr messages
150
+ :param on_result: Callback for the `Result` object
151
+ :param on_error: Callback for the `ExecutionError` object
152
+ :param envs: Custom environment variables
153
+ :param timeout: Timeout for the code execution in **seconds**
154
+ :param request_timeout: Timeout for the request in **seconds**
155
+
156
+ :return: `Execution` result object
157
+ """
158
+ ...
159
+
160
+ def run_code(
161
+ self,
162
+ code: str,
163
+ language: Optional[str] = None,
164
+ context: Optional[Context] = None,
165
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
166
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
167
+ on_result: Optional[OutputHandler[Result]] = None,
168
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
169
+ envs: Optional[Dict[str, str]] = None,
170
+ timeout: Optional[float] = None,
171
+ request_timeout: Optional[float] = None,
172
+ ) -> Execution:
173
+ logger.debug(f"Executing code {code}")
174
+
175
+ if language and context:
176
+ raise InvalidArgumentException(
177
+ "You can provide context or language, but not both at the same time."
178
+ )
179
+
180
+ timeout = None if timeout == 0 else (timeout or DEFAULT_TIMEOUT)
181
+ request_timeout = request_timeout or self._connection_config.request_timeout
182
+ context_id = context.id if context else None
183
+ client = api_pb2_connect.ExecutionServiceClient(
184
+ base_url=self.envd_api_url,
185
+ http_client=self._urllib3_pool,
186
+ )
187
+
188
+ # Create execution request
189
+ execute_request = api_pb2.ExecuteRequest(
190
+ code=code,
191
+ context_id=context_id or "",
192
+ language=language or "",
193
+ env_vars=envs,
194
+ )
195
+
196
+ try:
197
+ # Calculate deadline for gRPC call
198
+ deadline = time.time() + request_timeout + (timeout or 0)
199
+
200
+ # Execute code via gRPC
201
+ execution = Execution()
202
+ headers = {
203
+ "Authorization": "Bearer root",
204
+ }
205
+ response_stream = client.execute(
206
+ execute_request,
207
+ timeout_seconds=deadline - time.time(),
208
+ extra_headers=headers,
209
+ )
210
+
211
+ # Process stream responses
212
+ for response in response_stream:
213
+ # Convert gRPC response to the format expected by parse_output
214
+ # This assumes parse_output can handle gRPC response format
215
+ # You might need to adjust parse_output or convert the response here
216
+ parse_output(
217
+ execution,
218
+ response,
219
+ on_stdout=on_stdout,
220
+ on_stderr=on_stderr,
221
+ on_result=on_result,
222
+ on_error=on_error,
223
+ )
224
+
225
+ return execution
226
+
227
+ except Exception as e:
228
+ # Handle different types of timeout exceptions
229
+ if "timeout" in str(e).lower() or "deadline" in str(e).lower():
230
+ if "execution" in str(e).lower():
231
+ raise format_execution_timeout_error()
232
+ else:
233
+ raise format_request_timeout_error()
234
+ else:
235
+ # Re-raise other exceptions
236
+ raise e
237
+
238
+ def create_code_context(
239
+ self,
240
+ cwd: Optional[str] = None,
241
+ language: Optional[str] = None,
242
+ request_timeout: Optional[float] = None,
243
+ ) -> Context:
244
+ """
245
+ Creates a new context to run code in.
246
+
247
+ :param cwd: Set the current working directory for the context, defaults to `/home/user`
248
+ :param language: Language of the context. If not specified, defaults to Python
249
+ :param request_timeout: Timeout for the request in **milliseconds**
250
+
251
+ :return: Context object
252
+ """
253
+ logger.debug(f"Creating new {language} context")
254
+
255
+ # Create context request
256
+ create_context_request = api_pb2.CreateContextRequest(
257
+ language=language or "python3",
258
+ cwd=cwd or "",
259
+ )
260
+
261
+ try:
262
+ client = api_pb2_connect.ContextServiceClient(
263
+ base_url=self.envd_api_url,
264
+ http_client=self._urllib3_pool,
265
+ )
266
+ headers = {
267
+ "Authorization": "Bearer root",
268
+ }
269
+ # Create context via gRPC
270
+ response = client.create_context(
271
+ create_context_request,
272
+ timeout_seconds=request_timeout
273
+ or self._connection_config.request_timeout,
274
+ extra_headers=headers,
275
+ )
276
+
277
+ return Context.from_json(
278
+ {
279
+ "id": response.id,
280
+ "language": response.language,
281
+ "cwd": response.cwd,
282
+ }
283
+ )
284
+
285
+ except Exception as e:
286
+ # Handle timeout exceptions
287
+ if "timeout" in str(e).lower() or "deadline" in str(e).lower():
288
+ raise format_request_timeout_error()
289
+ else:
290
+ # Re-raise other exceptions
291
+ raise e
292
+
293
+ def destroy_context(self, context: Context) -> None:
294
+ """
295
+ Destroys a context.
296
+
297
+ :param context: Context to destroy
298
+ """
299
+ logger.debug(f"Destroying context {context.id}")
300
+
301
+ # Create destroy context request
302
+ destroy_context_request = api_pb2.DestroyContextRequest(
303
+ context_id=context.id,
304
+ )
305
+
306
+ try:
307
+ client = api_pb2_connect.ContextServiceClient(
308
+ base_url=self.envd_api_url,
309
+ http_client=self._urllib3_pool,
310
+ )
311
+ headers = {
312
+ "Authorization": "Bearer root",
313
+ }
314
+ client.destroy_context(destroy_context_request, extra_headers=headers)
315
+
316
+ except Exception as e:
317
+ logger.warning(f"Failed to destroy context {context.id}: {e}")
@@ -0,0 +1,3 @@
1
+ DEFAULT_TEMPLATE = "code-interpreter"
2
+ JUPYTER_PORT = 32000
3
+ DEFAULT_TIMEOUT = 300
@@ -0,0 +1,13 @@
1
+ from ..exceptions import TimeoutException
2
+
3
+
4
+ def format_request_timeout_error() -> Exception:
5
+ return TimeoutException(
6
+ f"Request timed out — the 'request_timeout' option can be used to increase this timeout",
7
+ )
8
+
9
+
10
+ def format_execution_timeout_error() -> Exception:
11
+ return TimeoutException(
12
+ f"Execution timed out — the 'timeout' option can be used to increase this timeout",
13
+ )