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,365 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import urllib.parse
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from packaging.version import Version
|
|
6
|
+
|
|
7
|
+
from ..api import AsyncApiClient, SandboxCreateResponse, handle_api_exception
|
|
8
|
+
from ..api.client.api.sandboxes import (
|
|
9
|
+
delete_sandboxes_sandbox_id,
|
|
10
|
+
get_sandboxes,
|
|
11
|
+
get_sandboxes_sandbox_id,
|
|
12
|
+
get_sandboxes_sandbox_id_metrics,
|
|
13
|
+
post_sandboxes,
|
|
14
|
+
post_sandboxes_sandbox_id_timeout,
|
|
15
|
+
)
|
|
16
|
+
from ..api.client.models import Error, NewSandbox, PostSandboxesSandboxIDTimeoutBody
|
|
17
|
+
from ..connection_config import ConnectionConfig, ProxyTypes
|
|
18
|
+
from ..exceptions import SandboxException, TemplateException
|
|
19
|
+
from ..sandbox.sandbox_api import (
|
|
20
|
+
ListedSandbox,
|
|
21
|
+
SandboxApiBase,
|
|
22
|
+
SandboxInfo,
|
|
23
|
+
SandboxMetrics,
|
|
24
|
+
SandboxQuery,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SandboxApi(SandboxApiBase):
|
|
29
|
+
@classmethod
|
|
30
|
+
async def list(
|
|
31
|
+
cls,
|
|
32
|
+
api_key: Optional[str] = None,
|
|
33
|
+
query: Optional[SandboxQuery] = None,
|
|
34
|
+
domain: Optional[str] = None,
|
|
35
|
+
debug: Optional[bool] = None,
|
|
36
|
+
request_timeout: Optional[float] = None,
|
|
37
|
+
headers: Optional[Dict[str, str]] = None,
|
|
38
|
+
proxy: Optional[ProxyTypes] = None,
|
|
39
|
+
) -> List[ListedSandbox]:
|
|
40
|
+
"""
|
|
41
|
+
List all running sandboxes.
|
|
42
|
+
|
|
43
|
+
:param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable
|
|
44
|
+
:param query: Filter the list of sandboxes, e.g. by metadata `SandboxQuery(metadata={"key": "value"})`, if there are multiple filters they are combined with AND.
|
|
45
|
+
:param domain: Domain to use for the request, only relevant for self-hosted environments
|
|
46
|
+
:param debug: Enable debug mode, all requested are then sent to localhost
|
|
47
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
48
|
+
:param headers: Additional headers to send with the request
|
|
49
|
+
:param proxy: Proxy to use for the request
|
|
50
|
+
|
|
51
|
+
:return: List of running sandboxes
|
|
52
|
+
"""
|
|
53
|
+
config = ConnectionConfig(
|
|
54
|
+
api_key=api_key,
|
|
55
|
+
domain=domain,
|
|
56
|
+
debug=debug,
|
|
57
|
+
request_timeout=request_timeout,
|
|
58
|
+
headers=headers,
|
|
59
|
+
proxy=proxy,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Convert filters to the format expected by the API
|
|
63
|
+
metadata = None
|
|
64
|
+
if query:
|
|
65
|
+
if query.metadata:
|
|
66
|
+
quoted_metadata = {
|
|
67
|
+
urllib.parse.quote(k): urllib.parse.quote(v)
|
|
68
|
+
for k, v in query.metadata.items()
|
|
69
|
+
}
|
|
70
|
+
metadata = urllib.parse.urlencode(quoted_metadata)
|
|
71
|
+
|
|
72
|
+
async with AsyncApiClient(
|
|
73
|
+
config,
|
|
74
|
+
limits=SandboxApiBase._limits,
|
|
75
|
+
) as api_client:
|
|
76
|
+
res = await get_sandboxes.asyncio_detailed(
|
|
77
|
+
client=api_client,
|
|
78
|
+
metadata=metadata,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if res.status_code >= 300:
|
|
82
|
+
raise handle_api_exception(res)
|
|
83
|
+
|
|
84
|
+
if res.parsed is None:
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
ListedSandbox(
|
|
89
|
+
sandbox_id=sandbox.sandbox_id,
|
|
90
|
+
template_id=sandbox.template_id,
|
|
91
|
+
name=sandbox.alias if isinstance(sandbox.alias, str) else None,
|
|
92
|
+
metadata=(
|
|
93
|
+
sandbox.metadata if isinstance(sandbox.metadata, dict) else {}
|
|
94
|
+
),
|
|
95
|
+
state=sandbox.state,
|
|
96
|
+
cpu_count=sandbox.cpu_count,
|
|
97
|
+
memory_mb=sandbox.memory_mb,
|
|
98
|
+
started_at=sandbox.started_at,
|
|
99
|
+
end_at=sandbox.end_at,
|
|
100
|
+
)
|
|
101
|
+
for sandbox in res.parsed
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
async def _cls_get_info(
|
|
106
|
+
cls,
|
|
107
|
+
sandbox_id: str,
|
|
108
|
+
api_key: Optional[str] = None,
|
|
109
|
+
domain: Optional[str] = None,
|
|
110
|
+
debug: Optional[bool] = None,
|
|
111
|
+
request_timeout: Optional[float] = None,
|
|
112
|
+
headers: Optional[Dict[str, str]] = None,
|
|
113
|
+
proxy: Optional[ProxyTypes] = None,
|
|
114
|
+
) -> SandboxInfo:
|
|
115
|
+
"""
|
|
116
|
+
Get the sandbox info.
|
|
117
|
+
:param sandbox_id: Sandbox ID
|
|
118
|
+
:param api_key: API key to use for authentication, defaults to `E2B_API_KEY` environment variable
|
|
119
|
+
:param domain: Domain to use for the request, defaults to `E2B_DOMAIN` environment variable
|
|
120
|
+
:param debug: Debug mode, defaults to `E2B_DEBUG` environment variable
|
|
121
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
122
|
+
:param headers: Additional headers to send with the request
|
|
123
|
+
:param proxy: Proxy to use for the request
|
|
124
|
+
|
|
125
|
+
:return: Sandbox info
|
|
126
|
+
"""
|
|
127
|
+
config = ConnectionConfig(
|
|
128
|
+
api_key=api_key,
|
|
129
|
+
domain=domain,
|
|
130
|
+
debug=debug,
|
|
131
|
+
request_timeout=request_timeout,
|
|
132
|
+
headers=headers,
|
|
133
|
+
proxy=proxy,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
async with AsyncApiClient(
|
|
137
|
+
config,
|
|
138
|
+
limits=SandboxApiBase._limits,
|
|
139
|
+
) as api_client:
|
|
140
|
+
res = await get_sandboxes_sandbox_id.asyncio_detailed(
|
|
141
|
+
sandbox_id,
|
|
142
|
+
client=api_client,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if res.status_code >= 300:
|
|
146
|
+
raise handle_api_exception(res)
|
|
147
|
+
|
|
148
|
+
if res.parsed is None:
|
|
149
|
+
raise Exception("Body of the request is None")
|
|
150
|
+
|
|
151
|
+
return SandboxInfo(
|
|
152
|
+
sandbox_id=res.parsed.sandbox_id,
|
|
153
|
+
sandbox_domain=res.parsed.domain,
|
|
154
|
+
template_id=res.parsed.template_id,
|
|
155
|
+
name=res.parsed.alias if isinstance(res.parsed.alias, str) else None,
|
|
156
|
+
metadata=(
|
|
157
|
+
res.parsed.metadata if isinstance(res.parsed.metadata, dict) else {}
|
|
158
|
+
),
|
|
159
|
+
started_at=res.parsed.started_at,
|
|
160
|
+
end_at=res.parsed.end_at,
|
|
161
|
+
envd_version=res.parsed.envd_version,
|
|
162
|
+
_envd_access_token=res.parsed.envd_access_token,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
async def _cls_kill(
|
|
167
|
+
cls,
|
|
168
|
+
sandbox_id: str,
|
|
169
|
+
api_key: Optional[str] = None,
|
|
170
|
+
domain: Optional[str] = None,
|
|
171
|
+
debug: Optional[bool] = None,
|
|
172
|
+
request_timeout: Optional[float] = None,
|
|
173
|
+
headers: Optional[Dict[str, str]] = None,
|
|
174
|
+
proxy: Optional[ProxyTypes] = None,
|
|
175
|
+
) -> bool:
|
|
176
|
+
config = ConnectionConfig(
|
|
177
|
+
api_key=api_key,
|
|
178
|
+
domain=domain,
|
|
179
|
+
debug=debug,
|
|
180
|
+
request_timeout=request_timeout,
|
|
181
|
+
headers=headers,
|
|
182
|
+
proxy=proxy,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if config.debug:
|
|
186
|
+
# Skip killing the sandbox in debug mode
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
async with AsyncApiClient(
|
|
190
|
+
config,
|
|
191
|
+
limits=SandboxApiBase._limits,
|
|
192
|
+
) as api_client:
|
|
193
|
+
res = await delete_sandboxes_sandbox_id.asyncio_detailed(
|
|
194
|
+
sandbox_id,
|
|
195
|
+
client=api_client,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if res.status_code == 404:
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
if res.status_code >= 300:
|
|
202
|
+
raise handle_api_exception(res)
|
|
203
|
+
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
async def _cls_set_timeout(
|
|
208
|
+
cls,
|
|
209
|
+
sandbox_id: str,
|
|
210
|
+
timeout: int,
|
|
211
|
+
api_key: Optional[str] = None,
|
|
212
|
+
domain: Optional[str] = None,
|
|
213
|
+
debug: Optional[bool] = None,
|
|
214
|
+
request_timeout: Optional[float] = None,
|
|
215
|
+
headers: Optional[Dict[str, str]] = None,
|
|
216
|
+
proxy: Optional[ProxyTypes] = None,
|
|
217
|
+
) -> None:
|
|
218
|
+
config = ConnectionConfig(
|
|
219
|
+
api_key=api_key,
|
|
220
|
+
domain=domain,
|
|
221
|
+
debug=debug,
|
|
222
|
+
request_timeout=request_timeout,
|
|
223
|
+
headers=headers,
|
|
224
|
+
proxy=proxy,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if config.debug:
|
|
228
|
+
# Skip setting the timeout in debug mode
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
async with AsyncApiClient(
|
|
232
|
+
config,
|
|
233
|
+
limits=SandboxApiBase._limits,
|
|
234
|
+
) as api_client:
|
|
235
|
+
res = await post_sandboxes_sandbox_id_timeout.asyncio_detailed(
|
|
236
|
+
sandbox_id,
|
|
237
|
+
client=api_client,
|
|
238
|
+
body=PostSandboxesSandboxIDTimeoutBody(timeout=timeout),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if res.status_code >= 300:
|
|
242
|
+
raise handle_api_exception(res)
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
async def _create_sandbox(
|
|
246
|
+
cls,
|
|
247
|
+
template: str,
|
|
248
|
+
timeout: int,
|
|
249
|
+
metadata: Optional[Dict[str, str]] = None,
|
|
250
|
+
env_vars: Optional[Dict[str, str]] = None,
|
|
251
|
+
secure: Optional[bool] = None,
|
|
252
|
+
api_key: Optional[str] = None,
|
|
253
|
+
domain: Optional[str] = None,
|
|
254
|
+
debug: Optional[bool] = None,
|
|
255
|
+
request_timeout: Optional[float] = None,
|
|
256
|
+
headers: Optional[Dict[str, str]] = None,
|
|
257
|
+
proxy: Optional[ProxyTypes] = None,
|
|
258
|
+
allow_internet_access: Optional[bool] = True,
|
|
259
|
+
) -> SandboxCreateResponse:
|
|
260
|
+
config = ConnectionConfig(
|
|
261
|
+
api_key=api_key,
|
|
262
|
+
domain=domain,
|
|
263
|
+
debug=debug,
|
|
264
|
+
request_timeout=request_timeout,
|
|
265
|
+
headers=headers,
|
|
266
|
+
proxy=proxy,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async with AsyncApiClient(
|
|
270
|
+
config,
|
|
271
|
+
limits=SandboxApiBase._limits,
|
|
272
|
+
) as api_client:
|
|
273
|
+
res = await post_sandboxes.asyncio_detailed(
|
|
274
|
+
body=NewSandbox(
|
|
275
|
+
template_id=template,
|
|
276
|
+
metadata=metadata or {},
|
|
277
|
+
timeout=timeout,
|
|
278
|
+
env_vars=env_vars or {},
|
|
279
|
+
secure=secure or False,
|
|
280
|
+
allow_internet_access=allow_internet_access,
|
|
281
|
+
is_async=False,
|
|
282
|
+
),
|
|
283
|
+
client=api_client,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if res.status_code >= 300:
|
|
287
|
+
raise handle_api_exception(res)
|
|
288
|
+
|
|
289
|
+
if res.parsed is None:
|
|
290
|
+
raise Exception("Body of the request is None")
|
|
291
|
+
|
|
292
|
+
# if Version(res.parsed.envd_version) < Version("0.1.0"):
|
|
293
|
+
# await SandboxApi._cls_kill(res.parsed.sandbox_id)
|
|
294
|
+
# raise TemplateException(
|
|
295
|
+
# "You need to update the template to use the new SDK. "
|
|
296
|
+
# "You can do this by running `e2b template build` in the directory with the template."
|
|
297
|
+
# )
|
|
298
|
+
|
|
299
|
+
return SandboxCreateResponse(
|
|
300
|
+
sandbox_id=res.parsed.sandbox_id,
|
|
301
|
+
sandbox_domain=res.parsed.domain,
|
|
302
|
+
envd_version=res.parsed.envd_version,
|
|
303
|
+
envd_access_token=res.parsed.envd_access_token,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
async def _cls_get_metrics(
|
|
308
|
+
cls,
|
|
309
|
+
sandbox_id: str,
|
|
310
|
+
start: Optional[datetime.datetime] = None,
|
|
311
|
+
end: Optional[datetime.datetime] = None,
|
|
312
|
+
api_key: Optional[str] = None,
|
|
313
|
+
domain: Optional[str] = None,
|
|
314
|
+
debug: Optional[bool] = None,
|
|
315
|
+
request_timeout: Optional[float] = None,
|
|
316
|
+
headers: Optional[Dict[str, str]] = None,
|
|
317
|
+
proxy: Optional[ProxyTypes] = None,
|
|
318
|
+
) -> List[SandboxMetrics]:
|
|
319
|
+
config = ConnectionConfig(
|
|
320
|
+
api_key=api_key,
|
|
321
|
+
domain=domain,
|
|
322
|
+
debug=debug,
|
|
323
|
+
headers=headers,
|
|
324
|
+
request_timeout=request_timeout,
|
|
325
|
+
proxy=proxy,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if config.debug:
|
|
329
|
+
# Skip getting the metrics in debug mode
|
|
330
|
+
return []
|
|
331
|
+
|
|
332
|
+
async with AsyncApiClient(
|
|
333
|
+
config,
|
|
334
|
+
limits=cls._limits,
|
|
335
|
+
) as api_client:
|
|
336
|
+
res = await get_sandboxes_sandbox_id_metrics.asyncio_detailed(
|
|
337
|
+
sandbox_id,
|
|
338
|
+
start=int(start.timestamp() * 1000) if start else None,
|
|
339
|
+
end=int(end.timestamp() * 1000) if end else None,
|
|
340
|
+
client=api_client,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if res.status_code >= 300:
|
|
344
|
+
raise handle_api_exception(res)
|
|
345
|
+
|
|
346
|
+
if res.parsed is None:
|
|
347
|
+
return []
|
|
348
|
+
|
|
349
|
+
# Check if res.parse is Error
|
|
350
|
+
if isinstance(res.parsed, Error):
|
|
351
|
+
raise SandboxException(f"{res.parsed.message}: Request failed")
|
|
352
|
+
|
|
353
|
+
# Convert to typed SandboxMetrics objects
|
|
354
|
+
return [
|
|
355
|
+
SandboxMetrics(
|
|
356
|
+
cpu_count=metric.cpu_count,
|
|
357
|
+
cpu_used_pct=metric.cpu_used_pct,
|
|
358
|
+
disk_total=metric.disk_total,
|
|
359
|
+
disk_used=metric.disk_used,
|
|
360
|
+
mem_total=metric.mem_total,
|
|
361
|
+
mem_used=metric.mem_used,
|
|
362
|
+
timestamp=metric.timestamp,
|
|
363
|
+
)
|
|
364
|
+
for metric in res.parsed
|
|
365
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
from typing import Callable, Dict, List, Literal, Optional, Union, overload
|
|
2
|
+
|
|
3
|
+
import httpcore
|
|
4
|
+
import urllib3
|
|
5
|
+
|
|
6
|
+
from ... import csx_connect
|
|
7
|
+
from ...connection_config import (
|
|
8
|
+
KEEPALIVE_PING_HEADER,
|
|
9
|
+
KEEPALIVE_PING_INTERVAL_SEC,
|
|
10
|
+
ConnectionConfig,
|
|
11
|
+
Username,
|
|
12
|
+
)
|
|
13
|
+
from ...exceptions import SandboxException
|
|
14
|
+
from ...generated import api_pb2, api_pb2_connect
|
|
15
|
+
from ...generated.rpc import authentication_header, handle_rpc_exception
|
|
16
|
+
from ...sandbox.commands.command_handle import CommandResult
|
|
17
|
+
from ...sandbox.commands.main import ProcessInfo
|
|
18
|
+
from ...sandbox_sync.commands.command_handle import CommandHandle
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Commands:
|
|
22
|
+
"""
|
|
23
|
+
Module for executing commands in the sandbox.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
envd_api_url: str,
|
|
29
|
+
connection_config: ConnectionConfig,
|
|
30
|
+
pool: urllib3.PoolManager,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._connection_config = connection_config
|
|
33
|
+
self._rpc = api_pb2_connect.ProcessClient(
|
|
34
|
+
envd_api_url,
|
|
35
|
+
http_client=pool,
|
|
36
|
+
)
|
|
37
|
+
self._headers = connection_config.headers
|
|
38
|
+
self._pool = pool
|
|
39
|
+
|
|
40
|
+
def list(
|
|
41
|
+
self,
|
|
42
|
+
request_timeout: Optional[float] = None,
|
|
43
|
+
) -> List[ProcessInfo]:
|
|
44
|
+
"""
|
|
45
|
+
Lists all running commands and PTY sessions.
|
|
46
|
+
|
|
47
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
48
|
+
|
|
49
|
+
:return: List of running commands and PTY sessions
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
res = self._rpc.list(
|
|
53
|
+
api_pb2.ListRequest(),
|
|
54
|
+
self._headers,
|
|
55
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
56
|
+
request_timeout
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
return [
|
|
60
|
+
ProcessInfo(
|
|
61
|
+
pid=p.pid,
|
|
62
|
+
tag=p.tag,
|
|
63
|
+
cmd=p.config.cmd,
|
|
64
|
+
args=list(p.config.args),
|
|
65
|
+
envs=dict(p.config.envs),
|
|
66
|
+
cwd=p.config.cwd,
|
|
67
|
+
)
|
|
68
|
+
for p in res.processes
|
|
69
|
+
]
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise handle_rpc_exception(e)
|
|
72
|
+
|
|
73
|
+
def kill(
|
|
74
|
+
self,
|
|
75
|
+
pid: int,
|
|
76
|
+
request_timeout: Optional[float] = None,
|
|
77
|
+
) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Kills a running command specified by its process ID.
|
|
80
|
+
It uses `SIGKILL` signal to kill the command.
|
|
81
|
+
|
|
82
|
+
:param pid: Process ID of the command. You can get the list of processes using `sandbox.commands.list()`
|
|
83
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
84
|
+
|
|
85
|
+
:return: `True` if the command was killed, `False` if the command was not found
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
self._rpc.send_signal(
|
|
89
|
+
api_pb2.SendSignalRequest(
|
|
90
|
+
process=api_pb2.ProcessSelector(pid=pid),
|
|
91
|
+
signal=api_pb2.Signal.SIGNAL_SIGKILL,
|
|
92
|
+
),
|
|
93
|
+
self._headers,
|
|
94
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
95
|
+
request_timeout
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
return True
|
|
99
|
+
except Exception as e:
|
|
100
|
+
if "not found" in str(e):
|
|
101
|
+
return False
|
|
102
|
+
raise handle_rpc_exception(e)
|
|
103
|
+
|
|
104
|
+
def send_stdin(
|
|
105
|
+
self,
|
|
106
|
+
pid: int,
|
|
107
|
+
data: str,
|
|
108
|
+
request_timeout: Optional[float] = None,
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Send data to command stdin.
|
|
112
|
+
|
|
113
|
+
:param pid Process ID of the command. You can get the list of processes using `sandbox.commands.list()`.
|
|
114
|
+
:param data: Data to send to the command
|
|
115
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
self._rpc.send_input(
|
|
119
|
+
api_pb2.SendInputRequest(
|
|
120
|
+
process=api_pb2.ProcessSelector(pid=pid),
|
|
121
|
+
input=api_pb2.ProcessInput(
|
|
122
|
+
stdin=data.encode(),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
self._headers,
|
|
126
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
127
|
+
request_timeout
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise handle_rpc_exception(e)
|
|
132
|
+
|
|
133
|
+
@overload
|
|
134
|
+
def run(
|
|
135
|
+
self,
|
|
136
|
+
cmd: str,
|
|
137
|
+
background: Union[Literal[False], None] = None,
|
|
138
|
+
envs: Optional[Dict[str, str]] = None,
|
|
139
|
+
user: Username = "user",
|
|
140
|
+
cwd: Optional[str] = None,
|
|
141
|
+
on_stdout: Optional[Callable[[str], None]] = None,
|
|
142
|
+
on_stderr: Optional[Callable[[str], None]] = None,
|
|
143
|
+
timeout: Optional[float] = 60,
|
|
144
|
+
request_timeout: Optional[float] = None,
|
|
145
|
+
) -> CommandResult:
|
|
146
|
+
"""
|
|
147
|
+
Start a new command and wait until it finishes executing.
|
|
148
|
+
|
|
149
|
+
:param cmd: Command to execute
|
|
150
|
+
:param background: **`False` if the command should be executed in the foreground**, `True` if the command should be executed in the background
|
|
151
|
+
:param envs: Environment variables used for the command
|
|
152
|
+
:param user: User to run the command as
|
|
153
|
+
:param cwd: Working directory to run the command
|
|
154
|
+
:param on_stdout: Callback for command stdout output
|
|
155
|
+
:param on_stderr: Callback for command stderr output
|
|
156
|
+
:param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
|
|
157
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
158
|
+
|
|
159
|
+
:return: `CommandResult` result of the command execution
|
|
160
|
+
"""
|
|
161
|
+
...
|
|
162
|
+
|
|
163
|
+
@overload
|
|
164
|
+
def run(
|
|
165
|
+
self,
|
|
166
|
+
cmd: str,
|
|
167
|
+
background: Literal[True],
|
|
168
|
+
envs: Optional[Dict[str, str]] = None,
|
|
169
|
+
user: Username = "user",
|
|
170
|
+
cwd: Optional[str] = None,
|
|
171
|
+
on_stdout: None = None,
|
|
172
|
+
on_stderr: None = None,
|
|
173
|
+
timeout: Optional[float] = 60,
|
|
174
|
+
request_timeout: Optional[float] = None,
|
|
175
|
+
) -> CommandHandle:
|
|
176
|
+
"""
|
|
177
|
+
Start a new command and return a handle to interact with it.
|
|
178
|
+
|
|
179
|
+
:param cmd: Command to execute
|
|
180
|
+
:param background: `False` if the command should be executed in the foreground, **`True` if the command should be executed in the background**
|
|
181
|
+
:param envs: Environment variables used for the command
|
|
182
|
+
:param user: User to run the command as
|
|
183
|
+
:param cwd: Working directory to run the command
|
|
184
|
+
:param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
|
|
185
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
186
|
+
|
|
187
|
+
:return: `CommandHandle` handle to interact with the running command
|
|
188
|
+
"""
|
|
189
|
+
...
|
|
190
|
+
|
|
191
|
+
def run(
|
|
192
|
+
self,
|
|
193
|
+
cmd: str,
|
|
194
|
+
background: Union[bool, None] = None,
|
|
195
|
+
envs: Optional[Dict[str, str]] = None,
|
|
196
|
+
user: Username = "user",
|
|
197
|
+
cwd: Optional[str] = None,
|
|
198
|
+
on_stdout: Optional[Callable[[str], None]] = None,
|
|
199
|
+
on_stderr: Optional[Callable[[str], None]] = None,
|
|
200
|
+
timeout: Optional[float] = 60,
|
|
201
|
+
request_timeout: Optional[float] = None,
|
|
202
|
+
):
|
|
203
|
+
if background:
|
|
204
|
+
cmd += " &"
|
|
205
|
+
proc = self._start(
|
|
206
|
+
cmd,
|
|
207
|
+
envs,
|
|
208
|
+
user,
|
|
209
|
+
cwd,
|
|
210
|
+
timeout,
|
|
211
|
+
request_timeout,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
proc
|
|
216
|
+
if background
|
|
217
|
+
else proc.wait(
|
|
218
|
+
on_stdout=on_stdout,
|
|
219
|
+
on_stderr=on_stderr,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _start(
|
|
224
|
+
self,
|
|
225
|
+
cmd: str,
|
|
226
|
+
envs: Optional[Dict[str, str]] = None,
|
|
227
|
+
user: Username = "user",
|
|
228
|
+
cwd: Optional[str] = None,
|
|
229
|
+
timeout: Optional[float] = 60,
|
|
230
|
+
request_timeout: Optional[float] = None,
|
|
231
|
+
):
|
|
232
|
+
events = self._rpc.start(
|
|
233
|
+
api_pb2.StartRequest(
|
|
234
|
+
process=api_pb2.ProcessConfig(
|
|
235
|
+
cmd="/bin/bash",
|
|
236
|
+
envs=envs,
|
|
237
|
+
args=["-l","-c", cmd],
|
|
238
|
+
cwd=cwd,
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
self._headers,
|
|
242
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
243
|
+
request_timeout
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
start_event = events.__next__()
|
|
249
|
+
|
|
250
|
+
if not start_event.HasField("event"):
|
|
251
|
+
raise SandboxException(
|
|
252
|
+
f"Failed to start process: expected start event, got {start_event}"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return CommandHandle(
|
|
256
|
+
pid=start_event.event.start.pid,
|
|
257
|
+
handle_kill=lambda: self.kill(start_event.event.start.pid),
|
|
258
|
+
events=events,
|
|
259
|
+
)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
raise handle_rpc_exception(e)
|
|
262
|
+
|
|
263
|
+
def connect(
|
|
264
|
+
self,
|
|
265
|
+
pid: int,
|
|
266
|
+
timeout: Optional[float] = 60,
|
|
267
|
+
request_timeout: Optional[float] = None,
|
|
268
|
+
):
|
|
269
|
+
"""
|
|
270
|
+
Connects to a running command.
|
|
271
|
+
You can use `CommandHandle.wait()` to wait for the command to finish and get execution results.
|
|
272
|
+
|
|
273
|
+
:param pid: Process ID of the command to connect to. You can get the list of processes using `sandbox.commands.list()`
|
|
274
|
+
:param timeout: Timeout for the connection in **seconds**. Using `0` will not limit the connection time
|
|
275
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
276
|
+
|
|
277
|
+
:return: `CommandHandle` handle to interact with the running command
|
|
278
|
+
"""
|
|
279
|
+
events = self._rpc.connect(
|
|
280
|
+
api_pb2.ConnectRequest(
|
|
281
|
+
process=api_pb2.ProcessSelector(pid=pid),
|
|
282
|
+
),
|
|
283
|
+
self._headers,
|
|
284
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
285
|
+
request_timeout
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
try:
|
|
289
|
+
start_event = events.__next__()
|
|
290
|
+
if not start_event.HasField("event"):
|
|
291
|
+
raise SandboxException(
|
|
292
|
+
f"Failed to connect to process: expected start event, got {start_event}"
|
|
293
|
+
)
|
|
294
|
+
return CommandHandle(
|
|
295
|
+
pid=start_event.event.start.pid,
|
|
296
|
+
handle_kill=lambda: self.kill(start_event.event.start.pid),
|
|
297
|
+
events=events,
|
|
298
|
+
)
|
|
299
|
+
except Exception as e:
|
|
300
|
+
raise handle_rpc_exception(e)
|