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.
- scalebox/__init__.py +80 -0
- scalebox/api/__init__.py +128 -0
- scalebox/api/client/__init__.py +8 -0
- scalebox/api/client/api/__init__.py +1 -0
- scalebox/api/client/api/sandboxes/__init__.py +0 -0
- scalebox/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
- scalebox/api/client/api/sandboxes/get_sandboxes.py +176 -0
- scalebox/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
- scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
- scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
- scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +214 -0
- scalebox/api/client/api/sandboxes/get_v2_sandboxes.py +229 -0
- scalebox/api/client/api/sandboxes/post_sandboxes.py +174 -0
- scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
- scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +182 -0
- scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +190 -0
- scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +194 -0
- scalebox/api/client/client.py +288 -0
- scalebox/api/client/errors.py +16 -0
- scalebox/api/client/models/__init__.py +81 -0
- scalebox/api/client/models/build_log_entry.py +79 -0
- scalebox/api/client/models/created_access_token.py +100 -0
- scalebox/api/client/models/created_team_api_key.py +166 -0
- scalebox/api/client/models/error.py +67 -0
- scalebox/api/client/models/identifier_masking_details.py +83 -0
- scalebox/api/client/models/listed_sandbox.py +138 -0
- scalebox/api/client/models/log_level.py +11 -0
- scalebox/api/client/models/new_access_token.py +59 -0
- scalebox/api/client/models/new_sandbox.py +125 -0
- scalebox/api/client/models/new_team_api_key.py +59 -0
- scalebox/api/client/models/node.py +154 -0
- scalebox/api/client/models/node_detail.py +152 -0
- scalebox/api/client/models/node_status.py +11 -0
- scalebox/api/client/models/node_status_change.py +61 -0
- scalebox/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
- scalebox/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
- scalebox/api/client/models/resumed_sandbox.py +68 -0
- scalebox/api/client/models/sandbox.py +125 -0
- scalebox/api/client/models/sandbox_detail.py +178 -0
- scalebox/api/client/models/sandbox_log.py +70 -0
- scalebox/api/client/models/sandbox_logs.py +73 -0
- scalebox/api/client/models/sandbox_metric.py +110 -0
- scalebox/api/client/models/sandbox_state.py +9 -0
- scalebox/api/client/models/sandboxes_with_metrics.py +59 -0
- scalebox/api/client/models/team.py +83 -0
- scalebox/api/client/models/team_api_key.py +158 -0
- scalebox/api/client/models/team_user.py +68 -0
- scalebox/api/client/models/template.py +179 -0
- scalebox/api/client/models/template_build.py +117 -0
- scalebox/api/client/models/template_build_file_upload.py +70 -0
- scalebox/api/client/models/template_build_request.py +115 -0
- scalebox/api/client/models/template_build_request_v2.py +88 -0
- scalebox/api/client/models/template_build_start_v2.py +114 -0
- scalebox/api/client/models/template_build_status.py +11 -0
- scalebox/api/client/models/template_step.py +91 -0
- scalebox/api/client/models/template_update_request.py +59 -0
- scalebox/api/client/models/update_team_api_key.py +59 -0
- scalebox/api/client/py.typed +1 -0
- scalebox/api/client/types.py +46 -0
- scalebox/api/metadata.py +19 -0
- scalebox/cli.py +125 -0
- scalebox/client/__init__.py +0 -0
- scalebox/client/aclient.py +57 -0
- scalebox/client/api.proto +460 -0
- scalebox/client/buf.gen.yaml +8 -0
- scalebox/client/client.py +102 -0
- scalebox/client/requirements.txt +5 -0
- scalebox/code_interpreter/__init__.py +12 -0
- scalebox/code_interpreter/charts.py +230 -0
- scalebox/code_interpreter/code_interpreter_async.py +369 -0
- scalebox/code_interpreter/code_interpreter_sync.py +317 -0
- scalebox/code_interpreter/constants.py +3 -0
- scalebox/code_interpreter/exceptions.py +13 -0
- scalebox/code_interpreter/models.py +485 -0
- scalebox/connection_config.py +92 -0
- scalebox/csx_connect/__init__.py +1 -0
- scalebox/csx_connect/client.py +485 -0
- scalebox/csx_desktop/__init__.py +0 -0
- scalebox/csx_desktop/main.py +651 -0
- scalebox/exceptions.py +83 -0
- scalebox/generated/__init__.py +0 -0
- scalebox/generated/api.py +61 -0
- scalebox/generated/api_pb2.py +203 -0
- scalebox/generated/api_pb2.pyi +956 -0
- scalebox/generated/api_pb2_connect.py +1456 -0
- scalebox/generated/rpc.py +50 -0
- scalebox/generated/versions.py +3 -0
- scalebox/requirements.txt +36 -0
- scalebox/sandbox/__init__.py +0 -0
- scalebox/sandbox/commands/__init__.py +0 -0
- scalebox/sandbox/commands/command_handle.py +69 -0
- scalebox/sandbox/commands/main.py +39 -0
- scalebox/sandbox/filesystem/__init__.py +0 -0
- scalebox/sandbox/filesystem/filesystem.py +95 -0
- scalebox/sandbox/filesystem/watch_handle.py +60 -0
- scalebox/sandbox/main.py +139 -0
- scalebox/sandbox/sandbox_api.py +91 -0
- scalebox/sandbox/signature.py +40 -0
- scalebox/sandbox/utils.py +34 -0
- scalebox/sandbox_async/__init__.py +1 -0
- scalebox/sandbox_async/commands/command.py +307 -0
- scalebox/sandbox_async/commands/command_handle.py +187 -0
- scalebox/sandbox_async/commands/pty.py +187 -0
- scalebox/sandbox_async/filesystem/filesystem.py +557 -0
- scalebox/sandbox_async/filesystem/watch_handle.py +61 -0
- scalebox/sandbox_async/main.py +646 -0
- scalebox/sandbox_async/sandbox_api.py +365 -0
- scalebox/sandbox_async/utils.py +7 -0
- scalebox/sandbox_sync/__init__.py +2 -0
- scalebox/sandbox_sync/commands/__init__.py +0 -0
- scalebox/sandbox_sync/commands/command.py +300 -0
- scalebox/sandbox_sync/commands/command_handle.py +150 -0
- scalebox/sandbox_sync/commands/pty.py +181 -0
- scalebox/sandbox_sync/filesystem/__init__.py +0 -0
- scalebox/sandbox_sync/filesystem/filesystem.py +543 -0
- scalebox/sandbox_sync/filesystem/watch_handle.py +66 -0
- scalebox/sandbox_sync/main.py +790 -0
- scalebox/sandbox_sync/sandbox_api.py +356 -0
- scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -0
- scalebox/test/README.md +329 -0
- scalebox/test/__init__.py +0 -0
- scalebox/test/aclient.py +72 -0
- scalebox/test/code_interpreter_centext.py +21 -0
- scalebox/test/code_interpreter_centext_sync.py +21 -0
- scalebox/test/code_interpreter_test.py +34 -0
- scalebox/test/code_interpreter_test_sync.py +34 -0
- scalebox/test/run_all_validation_tests.py +334 -0
- scalebox/test/run_code_interpreter_tests.sh +67 -0
- scalebox/test/run_tests.sh +230 -0
- scalebox/test/test_basic.py +78 -0
- scalebox/test/test_code_interpreter_async_comprehensive.py +2653 -0
- scalebox/test/test_code_interpreter_e2basync_comprehensive.py +2655 -0
- scalebox/test/test_code_interpreter_e2bsync_comprehensive.py +3416 -0
- scalebox/test/test_code_interpreter_sync_comprehensive.py +3412 -0
- scalebox/test/test_e2b_first.py +11 -0
- scalebox/test/test_sandbox_async_comprehensive.py +738 -0
- scalebox/test/test_sandbox_stress_and_edge_cases.py +778 -0
- scalebox/test/test_sandbox_sync_comprehensive.py +770 -0
- scalebox/test/test_sandbox_usage_examples.py +987 -0
- scalebox/test/testacreate.py +24 -0
- scalebox/test/testagetinfo.py +18 -0
- scalebox/test/testcodeinterpreter_async.py +508 -0
- scalebox/test/testcodeinterpreter_sync.py +239 -0
- scalebox/test/testcomputeuse.py +243 -0
- scalebox/test/testnovnc.py +12 -0
- scalebox/test/testsandbox_async.py +118 -0
- scalebox/test/testsandbox_sync.py +38 -0
- scalebox/utils/__init__.py +0 -0
- scalebox/utils/httpcoreclient.py +297 -0
- scalebox/utils/httpxclient.py +403 -0
- scalebox/version.py +16 -0
- scalebox_sdk-0.1.0.dist-info/METADATA +292 -0
- scalebox_sdk-0.1.0.dist-info/RECORD +157 -0
- scalebox_sdk-0.1.0.dist-info/WHEEL +5 -0
- scalebox_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- scalebox_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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,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
|
+
)
|