scalebox-sdk 0.1.24__py3-none-any.whl → 1.0.1__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 +2 -2
- scalebox/api/__init__.py +130 -128
- scalebox/api/client/__init__.py +8 -8
- scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +2 -2
- scalebox/api/client/api/sandboxes/post_sandboxes.py +2 -2
- scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- scalebox/api/client/client.py +288 -288
- scalebox/api/client/models/connect_sandbox.py +59 -0
- scalebox/api/client/models/error.py +2 -2
- scalebox/api/client/models/listed_sandbox.py +19 -1
- scalebox/api/client/models/new_sandbox.py +10 -0
- scalebox/api/client/models/sandbox.py +138 -125
- scalebox/api/client/models/sandbox_detail.py +24 -0
- scalebox/api/client/types.py +46 -46
- scalebox/cli.py +125 -125
- scalebox/client/aclient.py +57 -57
- scalebox/client/client.py +102 -102
- scalebox/code_interpreter/__init__.py +12 -12
- scalebox/code_interpreter/charts.py +230 -230
- scalebox/code_interpreter/constants.py +3 -3
- scalebox/code_interpreter/exceptions.py +13 -13
- scalebox/code_interpreter/models.py +485 -485
- scalebox/connection_config.py +34 -1
- scalebox/csx_connect/__init__.py +1 -1
- scalebox/csx_connect/client.py +485 -485
- scalebox/csx_desktop/main.py +651 -651
- scalebox/exceptions.py +83 -83
- scalebox/generated/api.py +61 -61
- scalebox/generated/api_pb2.py +203 -203
- scalebox/generated/api_pb2.pyi +956 -956
- scalebox/generated/api_pb2_connect.py +1407 -1407
- scalebox/generated/rpc.py +50 -50
- scalebox/sandbox/main.py +146 -139
- scalebox/sandbox/sandbox_api.py +105 -91
- scalebox/sandbox/signature.py +40 -40
- scalebox/sandbox/utils.py +34 -34
- scalebox/sandbox_async/commands/command.py +307 -307
- scalebox/sandbox_async/commands/command_handle.py +187 -187
- scalebox/sandbox_async/commands/pty.py +187 -187
- scalebox/sandbox_async/filesystem/filesystem.py +557 -557
- scalebox/sandbox_async/filesystem/watch_handle.py +61 -61
- scalebox/sandbox_async/main.py +228 -46
- scalebox/sandbox_async/sandbox_api.py +124 -3
- scalebox/sandbox_async/utils.py +7 -7
- scalebox/sandbox_sync/__init__.py +2 -2
- scalebox/sandbox_sync/commands/command.py +300 -300
- scalebox/sandbox_sync/commands/command_handle.py +150 -150
- scalebox/sandbox_sync/commands/pty.py +181 -181
- scalebox/sandbox_sync/filesystem/filesystem.py +3 -3
- scalebox/sandbox_sync/filesystem/watch_handle.py +66 -66
- scalebox/sandbox_sync/main.py +208 -133
- scalebox/sandbox_sync/sandbox_api.py +119 -3
- scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
- scalebox/test/README.md +329 -329
- scalebox/test/bedrock_openai_adapter.py +67 -0
- scalebox/test/code_interpreter_test.py +34 -34
- scalebox/test/code_interpreter_test_sync.py +34 -34
- scalebox/test/run_stress_code_interpreter_sync.py +166 -0
- scalebox/test/simple_upload_example.py +123 -0
- scalebox/test/stabitiy_test.py +310 -0
- scalebox/test/test_browser_use.py +25 -0
- scalebox/test/test_browser_use_scalebox.py +61 -0
- scalebox/test/test_code_interpreter_sync_comprehensive.py +115 -65
- scalebox/test/test_connect_pause_async.py +277 -0
- scalebox/test/test_connect_pause_sync.py +267 -0
- scalebox/test/test_desktop_sandbox_sf.py +117 -0
- scalebox/test/test_download_url.py +49 -0
- scalebox/test/test_sandbox_async_comprehensive.py +1 -1
- scalebox/test/test_sandbox_object_storage_example.py +146 -0
- scalebox/test/test_sandbox_object_storage_example_async.py +156 -0
- scalebox/test/test_sf.py +137 -0
- scalebox/test/test_watch_dir_async.py +56 -0
- scalebox/test/testacreate.py +1 -1
- scalebox/test/testagetinfo.py +1 -1
- scalebox/test/testcomputeuse.py +243 -243
- scalebox/test/testsandbox_api.py +13 -0
- scalebox/test/testsandbox_sync.py +1 -1
- scalebox/test/upload_100mb_example.py +355 -0
- scalebox/utils/httpcoreclient.py +297 -297
- scalebox/utils/httpxclient.py +403 -403
- scalebox/version.py +2 -2
- {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/METADATA +1 -1
- {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/RECORD +87 -69
- {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/WHEEL +1 -1
- {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/entry_points.txt +0 -0
- {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import inspect
|
|
3
|
-
from typing import Any, AsyncGenerator, Optional
|
|
4
|
-
|
|
5
|
-
from ...generated.api_pb2 import WatchDirResponse
|
|
6
|
-
from ...generated.rpc import handle_rpc_exception
|
|
7
|
-
from ...sandbox.filesystem.watch_handle import FilesystemEvent, map_event_type
|
|
8
|
-
from ...sandbox_async.utils import OutputHandler
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class AsyncWatchHandle:
|
|
12
|
-
"""
|
|
13
|
-
Handle for watching a directory in the sandbox filesystem.
|
|
14
|
-
|
|
15
|
-
Use `.stop()` to stop watching the directory.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(
|
|
19
|
-
self,
|
|
20
|
-
events: AsyncGenerator[WatchDirResponse, Any],
|
|
21
|
-
on_event: OutputHandler[FilesystemEvent],
|
|
22
|
-
on_exit: Optional[OutputHandler[Exception]] = None,
|
|
23
|
-
):
|
|
24
|
-
self._events = events
|
|
25
|
-
self._on_event = on_event
|
|
26
|
-
self._on_exit = on_exit
|
|
27
|
-
|
|
28
|
-
self._wait = asyncio.create_task(self._handle_events())
|
|
29
|
-
|
|
30
|
-
async def stop(self):
|
|
31
|
-
"""
|
|
32
|
-
Stop watching the directory.
|
|
33
|
-
"""
|
|
34
|
-
self._wait.cancel()
|
|
35
|
-
# BUG: In Python 3.8 closing async generator can throw RuntimeError.
|
|
36
|
-
# await self._events.aclose()
|
|
37
|
-
|
|
38
|
-
async def _iterate_events(self):
|
|
39
|
-
try:
|
|
40
|
-
async for event in self._events:
|
|
41
|
-
if event.HasField("filesystem"):
|
|
42
|
-
event_type = map_event_type(event.filesystem.type)
|
|
43
|
-
if event_type:
|
|
44
|
-
yield FilesystemEvent(
|
|
45
|
-
name=event.filesystem.name,
|
|
46
|
-
type=event_type,
|
|
47
|
-
)
|
|
48
|
-
except Exception as e:
|
|
49
|
-
raise handle_rpc_exception(e)
|
|
50
|
-
|
|
51
|
-
async def _handle_events(self):
|
|
52
|
-
try:
|
|
53
|
-
async for event in self._iterate_events():
|
|
54
|
-
cb = self._on_event(event)
|
|
55
|
-
if inspect.isawaitable(cb):
|
|
56
|
-
await cb
|
|
57
|
-
except Exception as e:
|
|
58
|
-
if self._on_exit:
|
|
59
|
-
cb = self._on_exit(e)
|
|
60
|
-
if inspect.isawaitable(cb):
|
|
61
|
-
await cb
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
from typing import Any, AsyncGenerator, Optional
|
|
4
|
+
|
|
5
|
+
from ...generated.api_pb2 import WatchDirResponse
|
|
6
|
+
from ...generated.rpc import handle_rpc_exception
|
|
7
|
+
from ...sandbox.filesystem.watch_handle import FilesystemEvent, map_event_type
|
|
8
|
+
from ...sandbox_async.utils import OutputHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncWatchHandle:
|
|
12
|
+
"""
|
|
13
|
+
Handle for watching a directory in the sandbox filesystem.
|
|
14
|
+
|
|
15
|
+
Use `.stop()` to stop watching the directory.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
events: AsyncGenerator[WatchDirResponse, Any],
|
|
21
|
+
on_event: OutputHandler[FilesystemEvent],
|
|
22
|
+
on_exit: Optional[OutputHandler[Exception]] = None,
|
|
23
|
+
):
|
|
24
|
+
self._events = events
|
|
25
|
+
self._on_event = on_event
|
|
26
|
+
self._on_exit = on_exit
|
|
27
|
+
|
|
28
|
+
self._wait = asyncio.create_task(self._handle_events())
|
|
29
|
+
|
|
30
|
+
async def stop(self):
|
|
31
|
+
"""
|
|
32
|
+
Stop watching the directory.
|
|
33
|
+
"""
|
|
34
|
+
self._wait.cancel()
|
|
35
|
+
# BUG: In Python 3.8 closing async generator can throw RuntimeError.
|
|
36
|
+
# await self._events.aclose()
|
|
37
|
+
|
|
38
|
+
async def _iterate_events(self):
|
|
39
|
+
try:
|
|
40
|
+
async for event in self._events:
|
|
41
|
+
if event.HasField("filesystem"):
|
|
42
|
+
event_type = map_event_type(event.filesystem.type)
|
|
43
|
+
if event_type:
|
|
44
|
+
yield FilesystemEvent(
|
|
45
|
+
name=event.filesystem.name,
|
|
46
|
+
type=event_type,
|
|
47
|
+
)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise handle_rpc_exception(e)
|
|
50
|
+
|
|
51
|
+
async def _handle_events(self):
|
|
52
|
+
try:
|
|
53
|
+
async for event in self._iterate_events():
|
|
54
|
+
cb = self._on_event(event)
|
|
55
|
+
if inspect.isawaitable(cb):
|
|
56
|
+
await cb
|
|
57
|
+
except Exception as e:
|
|
58
|
+
if self._on_exit:
|
|
59
|
+
cb = self._on_exit(e)
|
|
60
|
+
if inspect.isawaitable(cb):
|
|
61
|
+
await cb
|
scalebox/sandbox_async/main.py
CHANGED
|
@@ -6,10 +6,10 @@ from typing import Dict, List, Optional, TypedDict, overload
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
import httpx
|
|
8
8
|
from aiohttp import TCPConnector
|
|
9
|
-
from typing_extensions import Unpack
|
|
9
|
+
from typing_extensions import Unpack, Self
|
|
10
10
|
|
|
11
11
|
from ..api.client.types import Unset
|
|
12
|
-
from ..connection_config import ConnectionConfig, ProxyTypes
|
|
12
|
+
from ..connection_config import ConnectionConfig, ProxyTypes, ApiParams
|
|
13
13
|
from ..exceptions import SandboxException, request_timeout_error
|
|
14
14
|
from ..generated.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception
|
|
15
15
|
from ..sandbox.main import SandboxSetup
|
|
@@ -41,6 +41,8 @@ class AsyncSandboxOpts(TypedDict):
|
|
|
41
41
|
envd_version: Optional[str]
|
|
42
42
|
envd_access_token: Optional[str]
|
|
43
43
|
connection_config: ConnectionConfig
|
|
44
|
+
object_storage: Optional[Dict[str, str]]
|
|
45
|
+
network_proxy: Optional[Dict[str, any]]
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
@@ -101,6 +103,17 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
101
103
|
"""
|
|
102
104
|
return self._sandbox_domain
|
|
103
105
|
|
|
106
|
+
@property
|
|
107
|
+
def object_storage(self) -> Optional[Dict[str, str]]:
|
|
108
|
+
"""
|
|
109
|
+
Object storage configuration returned during sandbox creation (if any).
|
|
110
|
+
Only synchronous sandboxes currently expose this field.
|
|
111
|
+
"""
|
|
112
|
+
return self._object_storage
|
|
113
|
+
|
|
114
|
+
def network_proxy(self) -> Optional[Dict[str, object]]:
|
|
115
|
+
return self._network_proxy
|
|
116
|
+
|
|
104
117
|
@property
|
|
105
118
|
def envd_api_url(self) -> str:
|
|
106
119
|
return self._envd_api_url
|
|
@@ -126,7 +139,8 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
126
139
|
super().__init__()
|
|
127
140
|
|
|
128
141
|
self._connection_config = opts["connection_config"]
|
|
129
|
-
|
|
142
|
+
self._object_storage = opts["object_storage"]
|
|
143
|
+
self._network_proxy = opts["network_proxy"]
|
|
130
144
|
self._sandbox_id = opts["sandbox_id"]
|
|
131
145
|
self._sandbox_domain = opts["sandbox_domain"] or self.connection_config.domain
|
|
132
146
|
debug=self._connection_config.debug
|
|
@@ -222,13 +236,16 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
222
236
|
timeout: Optional[int] = None,
|
|
223
237
|
metadata: Optional[Dict[str, str]] = None,
|
|
224
238
|
envs: Optional[Dict[str, str]] = None,
|
|
239
|
+
object_storage: Optional[Dict[str, str]] = None,
|
|
225
240
|
api_key: Optional[str] = None,
|
|
226
241
|
domain: Optional[str] = None,
|
|
227
242
|
debug: Optional[bool] = None,
|
|
243
|
+
sandbox_id: Optional[str] = None,
|
|
228
244
|
request_timeout: Optional[float] = None,
|
|
229
245
|
proxy: Optional[ProxyTypes] = None,
|
|
230
246
|
secure: Optional[bool] = None,
|
|
231
247
|
allow_internet_access: Optional[bool] = True,
|
|
248
|
+
net_proxy_country: Optional[str] = None,
|
|
232
249
|
):
|
|
233
250
|
"""
|
|
234
251
|
Create a new sandbox.
|
|
@@ -251,11 +268,32 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
251
268
|
"""
|
|
252
269
|
|
|
253
270
|
connection_headers = {"Authorization": "Bearer root", }
|
|
271
|
+
network_proxy = {}
|
|
254
272
|
if debug:
|
|
255
273
|
sandbox_id = "debug_sandbox_id"
|
|
256
274
|
sandbox_domain = None
|
|
257
275
|
envd_version = None
|
|
258
276
|
envd_access_token = None
|
|
277
|
+
elif sandbox_id is not None:
|
|
278
|
+
response = await SandboxApi._cls_get_info(
|
|
279
|
+
sandbox_id,
|
|
280
|
+
api_key=api_key,
|
|
281
|
+
domain=domain,
|
|
282
|
+
debug=debug,
|
|
283
|
+
request_timeout=request_timeout,
|
|
284
|
+
proxy=proxy,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
sandbox_domain = response.sandbox_domain
|
|
288
|
+
envd_version = response.envd_version
|
|
289
|
+
envd_access_token = response._envd_access_token
|
|
290
|
+
object_storage = response.object_storage
|
|
291
|
+
network_proxy = response.network_proxy
|
|
292
|
+
|
|
293
|
+
if response._envd_access_token is not None and not isinstance(
|
|
294
|
+
response._envd_access_token, Unset
|
|
295
|
+
):
|
|
296
|
+
connection_headers["X-Access-Token"] = response._envd_access_token
|
|
259
297
|
else:
|
|
260
298
|
response = await SandboxApi._create_sandbox(
|
|
261
299
|
template=template or cls.default_template,
|
|
@@ -269,12 +307,16 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
269
307
|
secure=secure,
|
|
270
308
|
proxy=proxy,
|
|
271
309
|
allow_internet_access=allow_internet_access,
|
|
310
|
+
object_storage=object_storage,
|
|
311
|
+
net_proxy_country=net_proxy_country,
|
|
272
312
|
)
|
|
273
313
|
|
|
274
314
|
sandbox_id = response.sandbox_id
|
|
275
315
|
sandbox_domain = response.sandbox_domain
|
|
276
316
|
envd_version = response.envd_version
|
|
277
317
|
envd_access_token = response.envd_access_token
|
|
318
|
+
object_storage = response.object_storage
|
|
319
|
+
network_proxy = response.network_proxy
|
|
278
320
|
|
|
279
321
|
if envd_access_token is not None and not isinstance(
|
|
280
322
|
envd_access_token, Unset
|
|
@@ -297,9 +339,11 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
297
339
|
envd_version=envd_version,
|
|
298
340
|
envd_access_token=envd_access_token,
|
|
299
341
|
connection_config=connection_config,
|
|
342
|
+
object_storage=object_storage,
|
|
343
|
+
network_proxy=network_proxy
|
|
300
344
|
)
|
|
301
|
-
timeout =
|
|
302
|
-
interval = 0.
|
|
345
|
+
timeout = 10.0
|
|
346
|
+
interval = 0.3
|
|
303
347
|
elapsed = 0.0
|
|
304
348
|
|
|
305
349
|
while elapsed <= timeout:
|
|
@@ -312,69 +356,158 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
312
356
|
time.sleep(interval)
|
|
313
357
|
elapsed += interval
|
|
314
358
|
else:
|
|
315
|
-
print("connect "+sandbox_domain+ENVD_API_HEALTH_ROUTE +" timeout
|
|
359
|
+
print("connect "+sandbox_domain+ENVD_API_HEALTH_ROUTE +" timeout 10s")
|
|
316
360
|
return sanbox
|
|
317
361
|
|
|
362
|
+
@overload
|
|
363
|
+
async def connect(
|
|
364
|
+
self,
|
|
365
|
+
timeout: Optional[int] = None,
|
|
366
|
+
**opts: Unpack[ApiParams],
|
|
367
|
+
) -> Self:
|
|
368
|
+
"""
|
|
369
|
+
Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
|
|
370
|
+
Sandbox must be either running or be paused.
|
|
371
|
+
|
|
372
|
+
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
373
|
+
|
|
374
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
375
|
+
For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
|
|
376
|
+
:return: A running sandbox instance
|
|
377
|
+
|
|
378
|
+
@example
|
|
379
|
+
```python
|
|
380
|
+
sandbox = await AsyncSandbox.create()
|
|
381
|
+
await sandbox.beta_pause()
|
|
382
|
+
|
|
383
|
+
# Another code block
|
|
384
|
+
same_sandbox = await sandbox.connect()
|
|
385
|
+
```
|
|
386
|
+
"""
|
|
387
|
+
...
|
|
388
|
+
|
|
389
|
+
@overload
|
|
318
390
|
@classmethod
|
|
319
391
|
async def connect(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
proxy: Optional[ProxyTypes] = None,
|
|
326
|
-
):
|
|
392
|
+
cls,
|
|
393
|
+
sandbox_id: str,
|
|
394
|
+
timeout: Optional[int] = None,
|
|
395
|
+
**opts: Unpack[ApiParams],
|
|
396
|
+
) -> Self:
|
|
327
397
|
"""
|
|
328
|
-
Connect to
|
|
398
|
+
Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
|
|
399
|
+
Sandbox must be either running or be paused.
|
|
400
|
+
|
|
329
401
|
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
330
402
|
|
|
331
403
|
:param sandbox_id: Sandbox ID
|
|
332
|
-
:param
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
:return: sandbox instance for the existing sandbox
|
|
404
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
405
|
+
For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
|
|
406
|
+
:return: A running sandbox instance
|
|
336
407
|
|
|
337
408
|
@example
|
|
338
409
|
```python
|
|
339
410
|
sandbox = await AsyncSandbox.create()
|
|
340
|
-
|
|
411
|
+
await AsyncSandbox.beta_pause(sandbox.sandbox_id)
|
|
341
412
|
|
|
342
413
|
# Another code block
|
|
343
|
-
same_sandbox = await AsyncSandbox.connect(sandbox_id)
|
|
414
|
+
same_sandbox = await AsyncSandbox.connect(sandbox.sandbox_id))
|
|
415
|
+
```
|
|
344
416
|
"""
|
|
417
|
+
...
|
|
345
418
|
|
|
346
|
-
|
|
419
|
+
@class_method_variant("_cls_connect")
|
|
420
|
+
async def connect(
|
|
421
|
+
self,
|
|
422
|
+
timeout: Optional[int] = None,
|
|
423
|
+
**opts: Unpack[ApiParams],
|
|
424
|
+
) -> Self:
|
|
425
|
+
"""
|
|
426
|
+
Connect to a sandbox. If the sandbox is paused, it will be automatically resumed.
|
|
427
|
+
Sandbox must be either running or be paused.
|
|
347
428
|
|
|
348
|
-
|
|
349
|
-
sandbox_id,
|
|
350
|
-
api_key=api_key,
|
|
351
|
-
domain=domain,
|
|
352
|
-
debug=debug,
|
|
353
|
-
proxy=proxy,
|
|
354
|
-
)
|
|
429
|
+
With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
355
430
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
connection_headers["X-Access-Token"] = response._envd_access_token
|
|
360
|
-
print("connection_headers:"+str(connection_headers))
|
|
431
|
+
:param timeout: Timeout for the sandbox in **seconds**
|
|
432
|
+
For running sandboxes, the timeout will update only if the new timeout is longer than the existing one.
|
|
433
|
+
:return: A running sandbox instance
|
|
361
434
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
headers=connection_headers,
|
|
367
|
-
proxy=proxy,
|
|
368
|
-
)
|
|
435
|
+
@example
|
|
436
|
+
```python
|
|
437
|
+
sandbox = await AsyncSandbox.create()
|
|
438
|
+
await sandbox.beta_pause()
|
|
369
439
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
440
|
+
# Another code block
|
|
441
|
+
same_sandbox = await sandbox.connect()
|
|
442
|
+
```
|
|
443
|
+
"""
|
|
444
|
+
await SandboxApi._cls_connect(
|
|
445
|
+
sandbox_id=self.sandbox_id,
|
|
446
|
+
timeout=timeout,
|
|
447
|
+
**opts,
|
|
376
448
|
)
|
|
377
449
|
|
|
450
|
+
return self
|
|
451
|
+
# @classmethod
|
|
452
|
+
# async def connect(
|
|
453
|
+
# cls,
|
|
454
|
+
# sandbox_id: str,
|
|
455
|
+
# api_key: Optional[str] = None,
|
|
456
|
+
# domain: Optional[str] = None,
|
|
457
|
+
# debug: Optional[bool] = None,
|
|
458
|
+
# proxy: Optional[ProxyTypes] = None,
|
|
459
|
+
# ):
|
|
460
|
+
# """
|
|
461
|
+
# Connect to an existing sandbox.
|
|
462
|
+
# With sandbox ID you can connect to the same sandbox from different places or environments (serverless functions, etc).
|
|
463
|
+
#
|
|
464
|
+
# :param sandbox_id: Sandbox ID
|
|
465
|
+
# :param api_key: SBX API Key to use for authentication, defaults to `SBX_API_KEY` environment variable
|
|
466
|
+
# :param proxy: Proxy to use for the request and for the **requests made to the returned sandbox**
|
|
467
|
+
#
|
|
468
|
+
# :return: sandbox instance for the existing sandbox
|
|
469
|
+
#
|
|
470
|
+
# @example
|
|
471
|
+
# ```python
|
|
472
|
+
# sandbox = await AsyncSandbox.create()
|
|
473
|
+
# sandbox_id = sandbox.sandbox_id
|
|
474
|
+
#
|
|
475
|
+
# # Another code block
|
|
476
|
+
# same_sandbox = await AsyncSandbox.connect(sandbox_id)
|
|
477
|
+
# """
|
|
478
|
+
#
|
|
479
|
+
# connection_headers = {"Authorization": "Bearer root"}
|
|
480
|
+
#
|
|
481
|
+
# response = await SandboxApi._cls_get_info(
|
|
482
|
+
# sandbox_id,
|
|
483
|
+
# api_key=api_key,
|
|
484
|
+
# domain=domain,
|
|
485
|
+
# debug=debug,
|
|
486
|
+
# proxy=proxy,
|
|
487
|
+
# )
|
|
488
|
+
#
|
|
489
|
+
# if response._envd_access_token is not None and not isinstance(
|
|
490
|
+
# response._envd_access_token, Unset
|
|
491
|
+
# ):
|
|
492
|
+
# connection_headers["X-Access-Token"] = response._envd_access_token
|
|
493
|
+
# print("connection_headers:"+str(connection_headers))
|
|
494
|
+
#
|
|
495
|
+
# connection_config = ConnectionConfig(
|
|
496
|
+
# api_key=api_key,
|
|
497
|
+
# domain=domain,
|
|
498
|
+
# debug=debug,
|
|
499
|
+
# headers=connection_headers,
|
|
500
|
+
# proxy=proxy,
|
|
501
|
+
# )
|
|
502
|
+
#
|
|
503
|
+
# return cls(
|
|
504
|
+
# sandbox_id=sandbox_id,
|
|
505
|
+
# sandbox_domain=response.sandbox_domain,
|
|
506
|
+
# connection_config=connection_config,
|
|
507
|
+
# envd_version=response.envd_version,
|
|
508
|
+
# envd_access_token=response._envd_access_token,
|
|
509
|
+
# )
|
|
510
|
+
|
|
378
511
|
async def __aenter__(self):
|
|
379
512
|
if self._session.closed:
|
|
380
513
|
connector = TCPConnector(
|
|
@@ -645,3 +778,52 @@ class AsyncSandbox(SandboxSetup, SandboxApi):
|
|
|
645
778
|
end=end,
|
|
646
779
|
**config_dict,
|
|
647
780
|
)
|
|
781
|
+
|
|
782
|
+
@overload
|
|
783
|
+
async def beta_pause(
|
|
784
|
+
self,
|
|
785
|
+
**opts: Unpack[ApiParams],
|
|
786
|
+
) -> None:
|
|
787
|
+
"""
|
|
788
|
+
[BETA] This feature is in beta and may change in the future.
|
|
789
|
+
|
|
790
|
+
Pause the sandbox.
|
|
791
|
+
|
|
792
|
+
:return: Sandbox ID that can be used to resume the sandbox
|
|
793
|
+
"""
|
|
794
|
+
...
|
|
795
|
+
|
|
796
|
+
@overload
|
|
797
|
+
@staticmethod
|
|
798
|
+
async def beta_pause(
|
|
799
|
+
sandbox_id: str,
|
|
800
|
+
**opts: Unpack[ApiParams],
|
|
801
|
+
) -> None:
|
|
802
|
+
"""
|
|
803
|
+
[BETA] This feature is in beta and may change in the future.
|
|
804
|
+
|
|
805
|
+
Pause the sandbox specified by sandbox ID.
|
|
806
|
+
|
|
807
|
+
:param sandbox_id: Sandbox ID
|
|
808
|
+
|
|
809
|
+
:return: Sandbox ID that can be used to resume the sandbox
|
|
810
|
+
"""
|
|
811
|
+
...
|
|
812
|
+
|
|
813
|
+
@class_method_variant("_cls_pause")
|
|
814
|
+
async def beta_pause(
|
|
815
|
+
self,
|
|
816
|
+
**opts: Unpack[ApiParams],
|
|
817
|
+
) -> None:
|
|
818
|
+
"""
|
|
819
|
+
[BETA] This feature is in beta and may change in the future.
|
|
820
|
+
|
|
821
|
+
Pause the sandbox.
|
|
822
|
+
|
|
823
|
+
:return: Sandbox ID that can be used to resume the sandbox
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
await SandboxApi._cls_pause(
|
|
827
|
+
sandbox_id=self.sandbox_id,
|
|
828
|
+
**opts,
|
|
829
|
+
)
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import urllib.parse
|
|
3
|
-
from typing import Dict, List, Optional
|
|
3
|
+
from typing import Dict, List, Optional, Unpack, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from packaging.version import Version
|
|
6
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from . import AsyncSandbox
|
|
9
|
+
|
|
10
|
+
from ..api.client.api.sandboxes import post_sandboxes_sandbox_id_pause, post_sandboxes_sandbox_id_connect
|
|
11
|
+
from ..api.client.models.connect_sandbox import ConnectSandbox
|
|
7
12
|
from ..api import AsyncApiClient, SandboxCreateResponse, handle_api_exception
|
|
8
13
|
from ..api.client.api.sandboxes import (
|
|
9
14
|
delete_sandboxes_sandbox_id,
|
|
@@ -14,8 +19,8 @@ from ..api.client.api.sandboxes import (
|
|
|
14
19
|
post_sandboxes_sandbox_id_timeout,
|
|
15
20
|
)
|
|
16
21
|
from ..api.client.models import Error, NewSandbox, PostSandboxesSandboxIDTimeoutBody
|
|
17
|
-
from ..connection_config import ConnectionConfig, ProxyTypes
|
|
18
|
-
from ..exceptions import SandboxException, TemplateException
|
|
22
|
+
from ..connection_config import ConnectionConfig, ProxyTypes, ApiParams
|
|
23
|
+
from ..exceptions import SandboxException, TemplateException, NotFoundException
|
|
19
24
|
from ..sandbox.sandbox_api import (
|
|
20
25
|
ListedSandbox,
|
|
21
26
|
SandboxApiBase,
|
|
@@ -97,6 +102,9 @@ class SandboxApi(SandboxApiBase):
|
|
|
97
102
|
memory_mb=sandbox.memory_mb,
|
|
98
103
|
started_at=sandbox.started_at,
|
|
99
104
|
end_at=sandbox.end_at,
|
|
105
|
+
object_storage=sandbox.object_storage,
|
|
106
|
+
uptime=sandbox.uptime,
|
|
107
|
+
timeout=sandbox.timeout,
|
|
100
108
|
)
|
|
101
109
|
for sandbox in res.parsed
|
|
102
110
|
]
|
|
@@ -160,6 +168,9 @@ class SandboxApi(SandboxApiBase):
|
|
|
160
168
|
end_at=res.parsed.end_at,
|
|
161
169
|
envd_version=res.parsed.envd_version,
|
|
162
170
|
_envd_access_token=res.parsed.envd_access_token,
|
|
171
|
+
object_storage=res.parsed.object_storage,
|
|
172
|
+
uptime=res.parsed.uptime,
|
|
173
|
+
timeout=res.parsed.timeout,
|
|
163
174
|
)
|
|
164
175
|
|
|
165
176
|
@classmethod
|
|
@@ -256,6 +267,8 @@ class SandboxApi(SandboxApiBase):
|
|
|
256
267
|
headers: Optional[Dict[str, str]] = None,
|
|
257
268
|
proxy: Optional[ProxyTypes] = None,
|
|
258
269
|
allow_internet_access: Optional[bool] = True,
|
|
270
|
+
object_storage: Optional[Dict[str, str]] = None,
|
|
271
|
+
net_proxy_country:Optional[str] = None,
|
|
259
272
|
) -> SandboxCreateResponse:
|
|
260
273
|
config = ConnectionConfig(
|
|
261
274
|
api_key=api_key,
|
|
@@ -279,6 +292,8 @@ class SandboxApi(SandboxApiBase):
|
|
|
279
292
|
secure=secure or False,
|
|
280
293
|
allow_internet_access=allow_internet_access,
|
|
281
294
|
is_async=False,
|
|
295
|
+
object_storage=object_storage,
|
|
296
|
+
net_proxy_country=net_proxy_country,
|
|
282
297
|
),
|
|
283
298
|
client=api_client,
|
|
284
299
|
)
|
|
@@ -301,6 +316,8 @@ class SandboxApi(SandboxApiBase):
|
|
|
301
316
|
sandbox_domain=res.parsed.domain,
|
|
302
317
|
envd_version=res.parsed.envd_version,
|
|
303
318
|
envd_access_token=res.parsed.envd_access_token,
|
|
319
|
+
object_storage=res.parsed.object_storage,
|
|
320
|
+
network_proxy=res.parsed.network_proxy,
|
|
304
321
|
)
|
|
305
322
|
|
|
306
323
|
@classmethod
|
|
@@ -363,3 +380,107 @@ class SandboxApi(SandboxApiBase):
|
|
|
363
380
|
)
|
|
364
381
|
for metric in res.parsed
|
|
365
382
|
]
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
async def _cls_pause(
|
|
386
|
+
cls,
|
|
387
|
+
sandbox_id: str,
|
|
388
|
+
**opts: Unpack[ApiParams],
|
|
389
|
+
) -> str:
|
|
390
|
+
config = ConnectionConfig(**opts)
|
|
391
|
+
|
|
392
|
+
async with AsyncApiClient(
|
|
393
|
+
config,
|
|
394
|
+
limits=SandboxApiBase._limits,
|
|
395
|
+
) as api_client:
|
|
396
|
+
res = await post_sandboxes_sandbox_id_pause.asyncio_detailed(
|
|
397
|
+
sandbox_id,
|
|
398
|
+
client=api_client,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
if res.status_code == 404:
|
|
402
|
+
raise NotFoundException(f"Sandbox {sandbox_id} not found")
|
|
403
|
+
|
|
404
|
+
if res.status_code == 409:
|
|
405
|
+
return sandbox_id
|
|
406
|
+
|
|
407
|
+
if res.status_code >= 300:
|
|
408
|
+
raise handle_api_exception(res)
|
|
409
|
+
|
|
410
|
+
# Check if res.parse is Error
|
|
411
|
+
if isinstance(res.parsed, Error):
|
|
412
|
+
raise SandboxException(f"{res.parsed.message}: Request failed")
|
|
413
|
+
|
|
414
|
+
return sandbox_id
|
|
415
|
+
|
|
416
|
+
@classmethod
|
|
417
|
+
async def _cls_connect(
|
|
418
|
+
cls,
|
|
419
|
+
sandbox_id: str,
|
|
420
|
+
timeout: Optional[int] = None,
|
|
421
|
+
**opts: Unpack[ApiParams],
|
|
422
|
+
) -> "AsyncSandbox":
|
|
423
|
+
timeout = timeout or SandboxApiBase.default_sandbox_timeout
|
|
424
|
+
|
|
425
|
+
# Sandbox is not running, resume it
|
|
426
|
+
config = ConnectionConfig(**opts)
|
|
427
|
+
|
|
428
|
+
async with AsyncApiClient(
|
|
429
|
+
config,
|
|
430
|
+
limits=SandboxApiBase._limits,
|
|
431
|
+
) as api_client:
|
|
432
|
+
res = await post_sandboxes_sandbox_id_connect.asyncio_detailed(
|
|
433
|
+
sandbox_id,
|
|
434
|
+
client=api_client,
|
|
435
|
+
body=ConnectSandbox(timeout=timeout),
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if res.status_code == 404:
|
|
439
|
+
raise NotFoundException(f"Paused sandbox {sandbox_id} not found")
|
|
440
|
+
|
|
441
|
+
if res.status_code >= 300:
|
|
442
|
+
raise handle_api_exception(res)
|
|
443
|
+
|
|
444
|
+
# Check if res.parse is Error
|
|
445
|
+
if isinstance(res.parsed, Error):
|
|
446
|
+
raise SandboxException(f"{res.parsed.message}: Request failed")
|
|
447
|
+
|
|
448
|
+
# Extract information from API response and create a full AsyncSandbox instance
|
|
449
|
+
# Use delayed import to avoid circular dependency
|
|
450
|
+
from . import AsyncSandbox
|
|
451
|
+
|
|
452
|
+
response = res.parsed
|
|
453
|
+
if response is None:
|
|
454
|
+
raise SandboxException("Connect response is None")
|
|
455
|
+
|
|
456
|
+
connection_headers = {"Authorization": "Bearer root"}
|
|
457
|
+
|
|
458
|
+
# Extract fields from API response
|
|
459
|
+
sandbox_domain = response.domain if hasattr(response, 'domain') and response.domain is not None else None
|
|
460
|
+
envd_version = response.envd_version if hasattr(response, 'envd_version') else None
|
|
461
|
+
envd_access_token = None
|
|
462
|
+
if hasattr(response, 'envd_access_token'):
|
|
463
|
+
from ..api.client.types import Unset
|
|
464
|
+
if response.envd_access_token is not None and not isinstance(response.envd_access_token, Unset):
|
|
465
|
+
envd_access_token = response.envd_access_token
|
|
466
|
+
connection_headers["X-Access-Token"] = envd_access_token
|
|
467
|
+
|
|
468
|
+
connection_config = ConnectionConfig(
|
|
469
|
+
api_key=config.api_key,
|
|
470
|
+
domain=config.domain,
|
|
471
|
+
debug=config.debug,
|
|
472
|
+
request_timeout=config.request_timeout,
|
|
473
|
+
headers=connection_headers,
|
|
474
|
+
proxy=config.proxy,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# Create and return a full AsyncSandbox instance
|
|
478
|
+
sandbox = AsyncSandbox(
|
|
479
|
+
sandbox_id=sandbox_id,
|
|
480
|
+
sandbox_domain=sandbox_domain,
|
|
481
|
+
envd_version=envd_version,
|
|
482
|
+
envd_access_token=envd_access_token,
|
|
483
|
+
connection_config=connection_config,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
return sandbox
|