blaxel 0.2.36__py3-none-any.whl → 0.2.38__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.
- blaxel/__init__.py +2 -2
- blaxel/core/client/models/create_job_execution_request_env.py +3 -3
- blaxel/core/client/models/preview.py +48 -1
- blaxel/core/client/models/sandbox.py +10 -0
- blaxel/core/jobs/__init__.py +2 -2
- blaxel/core/sandbox/__init__.py +12 -0
- blaxel/core/sandbox/client/api/system/__init__.py +0 -0
- blaxel/core/sandbox/client/api/system/get_health.py +134 -0
- blaxel/core/sandbox/client/api/system/post_upgrade.py +196 -0
- blaxel/core/sandbox/client/models/__init__.py +8 -0
- blaxel/core/sandbox/client/models/content_search_match.py +24 -25
- blaxel/core/sandbox/client/models/content_search_response.py +25 -29
- blaxel/core/sandbox/client/models/find_match.py +13 -14
- blaxel/core/sandbox/client/models/find_response.py +21 -24
- blaxel/core/sandbox/client/models/fuzzy_search_match.py +17 -19
- blaxel/core/sandbox/client/models/fuzzy_search_response.py +21 -24
- blaxel/core/sandbox/client/models/health_response.py +159 -0
- blaxel/core/sandbox/client/models/process_upgrade_state.py +20 -0
- blaxel/core/sandbox/client/models/upgrade_request.py +71 -0
- blaxel/core/sandbox/client/models/upgrade_status.py +125 -0
- blaxel/core/sandbox/default/__init__.py +2 -0
- blaxel/core/sandbox/default/filesystem.py +20 -6
- blaxel/core/sandbox/default/preview.py +48 -1
- blaxel/core/sandbox/default/process.py +66 -21
- blaxel/core/sandbox/default/sandbox.py +36 -5
- blaxel/core/sandbox/default/system.py +71 -0
- blaxel/core/sandbox/sync/__init__.py +2 -0
- blaxel/core/sandbox/sync/filesystem.py +19 -2
- blaxel/core/sandbox/sync/preview.py +50 -3
- blaxel/core/sandbox/sync/process.py +38 -15
- blaxel/core/sandbox/sync/sandbox.py +29 -4
- blaxel/core/sandbox/sync/system.py +71 -0
- blaxel/core/sandbox/types.py +212 -5
- blaxel/core/tools/__init__.py +4 -0
- blaxel/core/volume/volume.py +10 -0
- blaxel/crewai/model.py +81 -44
- blaxel/crewai/tools.py +85 -2
- blaxel/googleadk/model.py +22 -3
- blaxel/googleadk/tools.py +25 -6
- blaxel/langgraph/custom/gemini.py +19 -12
- blaxel/langgraph/model.py +26 -18
- blaxel/langgraph/tools.py +6 -12
- blaxel/livekit/model.py +7 -2
- blaxel/livekit/tools.py +3 -1
- blaxel/llamaindex/model.py +145 -84
- blaxel/llamaindex/tools.py +6 -4
- blaxel/openai/model.py +7 -1
- blaxel/openai/tools.py +13 -3
- blaxel/pydantic/model.py +38 -24
- blaxel/pydantic/tools.py +37 -4
- blaxel-0.2.38.dist-info/METADATA +528 -0
- {blaxel-0.2.36.dist-info → blaxel-0.2.38.dist-info}/RECORD +54 -45
- blaxel-0.2.36.dist-info/METADATA +0 -228
- {blaxel-0.2.36.dist-info → blaxel-0.2.38.dist-info}/WHEEL +0 -0
- {blaxel-0.2.36.dist-info → blaxel-0.2.38.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from ...common.settings import settings
|
|
4
|
+
from ..client.api.system.get_health import asyncio as get_health
|
|
5
|
+
from ..client.api.system.post_upgrade import asyncio as post_upgrade
|
|
6
|
+
from ..client.client import Client
|
|
7
|
+
from ..client.models import ErrorResponse, HealthResponse, SuccessResponse, UpgradeRequest
|
|
8
|
+
from ..types import SandboxConfiguration
|
|
9
|
+
from .action import SandboxAction
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SandboxSystem(SandboxAction):
|
|
13
|
+
"""System operations for sandbox including upgrade functionality."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, sandbox_config: SandboxConfiguration):
|
|
16
|
+
super().__init__(sandbox_config)
|
|
17
|
+
|
|
18
|
+
async def upgrade(
|
|
19
|
+
self,
|
|
20
|
+
version: str | None = None,
|
|
21
|
+
base_url: str | None = None,
|
|
22
|
+
) -> SuccessResponse:
|
|
23
|
+
"""Upgrade the sandbox-api to a new version.
|
|
24
|
+
|
|
25
|
+
Triggers an upgrade of the sandbox-api process. Returns immediately before upgrading.
|
|
26
|
+
The upgrade will: download the specified binary from GitHub releases, validate it, and restart.
|
|
27
|
+
All running processes will be preserved across the upgrade.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
version: Version to upgrade to - "develop" (default), "main", "latest",
|
|
31
|
+
or specific tag like "v1.0.0"
|
|
32
|
+
base_url: Base URL for releases (useful for forks, defaults to
|
|
33
|
+
https://github.com/blaxel-ai/sandbox/releases)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
SuccessResponse with status information
|
|
37
|
+
"""
|
|
38
|
+
request = UpgradeRequest(version=version, base_url=base_url)
|
|
39
|
+
|
|
40
|
+
client = Client(
|
|
41
|
+
base_url=self.url,
|
|
42
|
+
headers={**settings.headers, **self.sandbox_config.headers},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async with client:
|
|
46
|
+
response = await post_upgrade(client=client, body=request)
|
|
47
|
+
if response is None:
|
|
48
|
+
raise Exception("Failed to upgrade sandbox")
|
|
49
|
+
if isinstance(response, ErrorResponse):
|
|
50
|
+
raise Exception(f"Upgrade failed: {response.error}")
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
async def health(self) -> HealthResponse:
|
|
54
|
+
"""Get health status and system information.
|
|
55
|
+
|
|
56
|
+
Returns health status and system information including upgrade count and binary details.
|
|
57
|
+
Also includes last upgrade attempt status with detailed error information if available.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
HealthResponse with system status information
|
|
61
|
+
"""
|
|
62
|
+
client = Client(
|
|
63
|
+
base_url=self.url,
|
|
64
|
+
headers={**settings.headers, **self.sandbox_config.headers},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async with client:
|
|
68
|
+
response = await get_health(client=client)
|
|
69
|
+
if response is None:
|
|
70
|
+
raise Exception("Failed to get health status")
|
|
71
|
+
return response
|
|
@@ -6,6 +6,7 @@ from .sandbox import (
|
|
|
6
6
|
SyncSandboxPreviews,
|
|
7
7
|
SyncSandboxProcess,
|
|
8
8
|
)
|
|
9
|
+
from .system import SyncSandboxSystem
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
"SyncSandboxInstance",
|
|
@@ -13,5 +14,6 @@ __all__ = [
|
|
|
13
14
|
"SyncSandboxPreviews",
|
|
14
15
|
"SyncSandboxProcess",
|
|
15
16
|
"SyncSandboxCodegen",
|
|
17
|
+
"SyncSandboxSystem",
|
|
16
18
|
"SyncCodeInterpreter",
|
|
17
19
|
]
|
|
@@ -14,6 +14,7 @@ from ..types import (
|
|
|
14
14
|
SandboxConfiguration,
|
|
15
15
|
SandboxFilesystemFile,
|
|
16
16
|
WatchEvent,
|
|
17
|
+
WatchHandle,
|
|
17
18
|
)
|
|
18
19
|
from .action import SyncSandboxAction
|
|
19
20
|
|
|
@@ -162,7 +163,23 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
162
163
|
path: str,
|
|
163
164
|
callback: Callable[[WatchEvent], None],
|
|
164
165
|
options: Dict[str, Any] | None = None,
|
|
165
|
-
) ->
|
|
166
|
+
) -> WatchHandle:
|
|
167
|
+
"""Watch for file system changes.
|
|
168
|
+
|
|
169
|
+
Returns a WatchHandle that can be used as a context manager:
|
|
170
|
+
|
|
171
|
+
with sandbox.fs.watch(path, callback) as handle:
|
|
172
|
+
# do something
|
|
173
|
+
# handle is automatically closed
|
|
174
|
+
|
|
175
|
+
Or manually:
|
|
176
|
+
|
|
177
|
+
handle = sandbox.fs.watch(path, callback)
|
|
178
|
+
try:
|
|
179
|
+
# do something
|
|
180
|
+
finally:
|
|
181
|
+
handle.close()
|
|
182
|
+
"""
|
|
166
183
|
path = self.format_path(path)
|
|
167
184
|
closed = threading.Event()
|
|
168
185
|
if options is None:
|
|
@@ -226,7 +243,7 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
226
243
|
def close():
|
|
227
244
|
closed.set()
|
|
228
245
|
|
|
229
|
-
return
|
|
246
|
+
return WatchHandle(close)
|
|
230
247
|
|
|
231
248
|
def format_path(self, path: str) -> str:
|
|
232
249
|
return path
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import time
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
from typing import Any, Dict, List, Union
|
|
@@ -11,6 +12,9 @@ from ...client.api.compute.delete_sandbox_preview import sync as delete_sandbox_
|
|
|
11
12
|
from ...client.api.compute.delete_sandbox_preview_token import (
|
|
12
13
|
sync as delete_sandbox_preview_token,
|
|
13
14
|
)
|
|
15
|
+
from ...client.api.compute.get_sandbox_preview import (
|
|
16
|
+
sync_detailed as get_sandbox_preview_detailed,
|
|
17
|
+
)
|
|
14
18
|
from ...client.api.compute.get_sandbox_preview import sync as get_sandbox_preview
|
|
15
19
|
from ...client.api.compute.list_sandbox_preview_tokens import (
|
|
16
20
|
sync as list_sandbox_preview_tokens,
|
|
@@ -188,9 +192,52 @@ class SyncSandboxPreviews:
|
|
|
188
192
|
preview_name,
|
|
189
193
|
client=client,
|
|
190
194
|
)
|
|
191
|
-
if response:
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
if not response:
|
|
196
|
+
raise errors.UnexpectedStatus(400, b"Failed to delete preview")
|
|
197
|
+
|
|
198
|
+
# If the preview is in DELETING state, wait for it to be fully deleted
|
|
199
|
+
if response.status == "DELETING":
|
|
200
|
+
self._wait_for_deletion(preview_name)
|
|
201
|
+
|
|
202
|
+
return response
|
|
203
|
+
|
|
204
|
+
def _wait_for_deletion(self, preview_name: str, timeout_ms: int = 10000) -> None:
|
|
205
|
+
"""Wait for a preview to be fully deleted.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
preview_name: Name of the preview to wait for
|
|
209
|
+
timeout_ms: Timeout in milliseconds (default: 10000)
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
Exception: If the preview is still in DELETING state after timeout
|
|
213
|
+
"""
|
|
214
|
+
print(f"Waiting for preview deletion: {preview_name}")
|
|
215
|
+
poll_interval = 0.5 # Poll every 500ms
|
|
216
|
+
elapsed = 0.0
|
|
217
|
+
timeout_seconds = timeout_ms / 1000.0
|
|
218
|
+
|
|
219
|
+
while elapsed < timeout_seconds:
|
|
220
|
+
try:
|
|
221
|
+
response = get_sandbox_preview_detailed(
|
|
222
|
+
self.sandbox_name,
|
|
223
|
+
preview_name,
|
|
224
|
+
client=client,
|
|
225
|
+
)
|
|
226
|
+
if response.status_code == 404:
|
|
227
|
+
return
|
|
228
|
+
except errors.UnexpectedStatus as e:
|
|
229
|
+
# 404 means the preview is deleted
|
|
230
|
+
if e.status_code == 404:
|
|
231
|
+
return
|
|
232
|
+
raise
|
|
233
|
+
# Preview still exists, wait and retry
|
|
234
|
+
time.sleep(poll_interval)
|
|
235
|
+
elapsed += poll_interval
|
|
236
|
+
|
|
237
|
+
# Timeout reached, but deletion was initiated
|
|
238
|
+
raise Exception(
|
|
239
|
+
f"Preview deletion timeout: {preview_name} is still in DELETING state after {timeout_ms}ms"
|
|
240
|
+
)
|
|
194
241
|
|
|
195
242
|
|
|
196
243
|
def to_utc_z(dt: datetime) -> str:
|
|
@@ -7,7 +7,12 @@ import httpx
|
|
|
7
7
|
from ...common.settings import settings
|
|
8
8
|
from ..client.models import ProcessResponse, SuccessResponse
|
|
9
9
|
from ..client.models.process_request import ProcessRequest
|
|
10
|
-
from ..types import
|
|
10
|
+
from ..types import (
|
|
11
|
+
ProcessRequestWithLog,
|
|
12
|
+
ProcessResponseWithLog,
|
|
13
|
+
SandboxConfiguration,
|
|
14
|
+
StreamHandle,
|
|
15
|
+
)
|
|
11
16
|
from .action import SyncSandboxAction
|
|
12
17
|
|
|
13
18
|
|
|
@@ -19,19 +24,35 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
19
24
|
self,
|
|
20
25
|
process_name: str,
|
|
21
26
|
options: Dict[str, Callable[[str], None]] | None = None,
|
|
22
|
-
) ->
|
|
27
|
+
) -> StreamHandle:
|
|
28
|
+
"""Stream logs from a process with automatic reconnection and deduplication.
|
|
29
|
+
|
|
30
|
+
Returns a StreamHandle that can be used as a context manager:
|
|
31
|
+
|
|
32
|
+
with sandbox.process.stream_logs(name, options) as handle:
|
|
33
|
+
# do something
|
|
34
|
+
# handle is automatically closed
|
|
35
|
+
|
|
36
|
+
Or manually:
|
|
37
|
+
|
|
38
|
+
handle = sandbox.process.stream_logs(name, options)
|
|
39
|
+
try:
|
|
40
|
+
# do something
|
|
41
|
+
finally:
|
|
42
|
+
handle.close()
|
|
43
|
+
"""
|
|
23
44
|
if options is None:
|
|
24
45
|
options = {}
|
|
25
46
|
reconnect_interval = 30
|
|
26
47
|
is_running = threading.Event()
|
|
27
48
|
is_running.set()
|
|
28
49
|
seen_logs = set()
|
|
29
|
-
|
|
50
|
+
current_stream: StreamHandle | None = None
|
|
30
51
|
timer_lock = threading.Lock()
|
|
31
|
-
reconnect_timer = {"t": None}
|
|
52
|
+
reconnect_timer: dict[str, threading.Timer | None] = {"t": None}
|
|
32
53
|
|
|
33
54
|
def start_stream():
|
|
34
|
-
nonlocal
|
|
55
|
+
nonlocal current_stream
|
|
35
56
|
log_counter = [0]
|
|
36
57
|
|
|
37
58
|
def make_dedup(cb_key: str):
|
|
@@ -52,9 +73,9 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
52
73
|
wrapped_options["on_stdout"] = make_dedup("on_stdout")
|
|
53
74
|
if "on_stderr" in options:
|
|
54
75
|
wrapped_options["on_stderr"] = make_dedup("on_stderr")
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
if current_stream:
|
|
77
|
+
current_stream.close()
|
|
78
|
+
current_stream = self._stream_logs(process_name, wrapped_options)
|
|
58
79
|
|
|
59
80
|
def schedule():
|
|
60
81
|
if is_running.is_set():
|
|
@@ -71,23 +92,25 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
71
92
|
start_stream()
|
|
72
93
|
|
|
73
94
|
def close():
|
|
95
|
+
nonlocal current_stream
|
|
74
96
|
is_running.clear()
|
|
75
97
|
with timer_lock:
|
|
76
98
|
if reconnect_timer["t"]:
|
|
77
99
|
reconnect_timer["t"].cancel()
|
|
78
100
|
reconnect_timer["t"] = None
|
|
79
|
-
if
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
if current_stream:
|
|
102
|
+
current_stream.close()
|
|
103
|
+
current_stream = None
|
|
82
104
|
seen_logs.clear()
|
|
83
105
|
|
|
84
|
-
return
|
|
106
|
+
return StreamHandle(close)
|
|
85
107
|
|
|
86
108
|
def _stream_logs(
|
|
87
109
|
self,
|
|
88
110
|
identifier: str,
|
|
89
111
|
options: Dict[str, Callable[[str], None]] | None = None,
|
|
90
|
-
) ->
|
|
112
|
+
) -> StreamHandle:
|
|
113
|
+
"""Private method to stream logs from a process with callbacks for different output types."""
|
|
91
114
|
if options is None:
|
|
92
115
|
options = {}
|
|
93
116
|
closed = threading.Event()
|
|
@@ -136,7 +159,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
136
159
|
def close():
|
|
137
160
|
closed.set()
|
|
138
161
|
|
|
139
|
-
return
|
|
162
|
+
return StreamHandle(close)
|
|
140
163
|
|
|
141
164
|
def exec(
|
|
142
165
|
self,
|
|
@@ -191,7 +214,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
191
214
|
)
|
|
192
215
|
return ProcessResponseWithLog(
|
|
193
216
|
result,
|
|
194
|
-
lambda: stream_control
|
|
217
|
+
lambda: stream_control.close() if stream_control else None,
|
|
195
218
|
)
|
|
196
219
|
|
|
197
220
|
return result
|
|
@@ -8,7 +8,13 @@ from ...client.api.compute.get_sandbox import sync as get_sandbox
|
|
|
8
8
|
from ...client.api.compute.list_sandboxes import sync as list_sandboxes
|
|
9
9
|
from ...client.api.compute.update_sandbox import sync as update_sandbox
|
|
10
10
|
from ...client.client import client
|
|
11
|
-
from ...client.models import
|
|
11
|
+
from ...client.models import (
|
|
12
|
+
Metadata,
|
|
13
|
+
Sandbox,
|
|
14
|
+
SandboxLifecycle,
|
|
15
|
+
SandboxRuntime,
|
|
16
|
+
SandboxSpec,
|
|
17
|
+
)
|
|
12
18
|
from ...client.models.error import Error
|
|
13
19
|
from ...client.models.sandbox_error import SandboxError
|
|
14
20
|
from ...client.types import UNSET
|
|
@@ -26,6 +32,7 @@ from .network import SyncSandboxNetwork
|
|
|
26
32
|
from .preview import SyncSandboxPreviews
|
|
27
33
|
from .process import SyncSandboxProcess
|
|
28
34
|
from .session import SyncSandboxSessions
|
|
35
|
+
from .system import SyncSandboxSystem
|
|
29
36
|
|
|
30
37
|
logger = logging.getLogger(__name__)
|
|
31
38
|
|
|
@@ -73,6 +80,7 @@ class SyncSandboxInstance:
|
|
|
73
80
|
self.sessions = SyncSandboxSessions(self.config)
|
|
74
81
|
self.network = SyncSandboxNetwork(self.config)
|
|
75
82
|
self.codegen = SyncSandboxCodegen(self.config)
|
|
83
|
+
self.system = SyncSandboxSystem(self.config)
|
|
76
84
|
|
|
77
85
|
@property
|
|
78
86
|
def metadata(self):
|
|
@@ -90,6 +98,14 @@ class SyncSandboxInstance:
|
|
|
90
98
|
def spec(self):
|
|
91
99
|
return self.sandbox.spec
|
|
92
100
|
|
|
101
|
+
@property
|
|
102
|
+
def last_used_at(self):
|
|
103
|
+
return self.sandbox.last_used_at
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def expires_in(self):
|
|
107
|
+
return self.sandbox.expires_in
|
|
108
|
+
|
|
93
109
|
def wait(self, max_wait: int = 60000, interval: int = 1000) -> "SyncSandboxInstance":
|
|
94
110
|
logger.warning(
|
|
95
111
|
"⚠️ Warning: sandbox.wait() is deprecated. You don't need to wait for the sandbox to be deployed anymore."
|
|
@@ -159,17 +175,26 @@ class SyncSandboxInstance:
|
|
|
159
175
|
volumes=volumes,
|
|
160
176
|
),
|
|
161
177
|
)
|
|
162
|
-
if ttl:
|
|
178
|
+
if ttl and sandbox.spec.runtime:
|
|
163
179
|
sandbox.spec.runtime.ttl = ttl
|
|
164
|
-
if expires:
|
|
180
|
+
if expires and sandbox.spec.runtime:
|
|
165
181
|
sandbox.spec.runtime.expires = expires.isoformat()
|
|
166
182
|
if region:
|
|
167
183
|
sandbox.spec.region = region
|
|
168
184
|
if lifecycle:
|
|
169
|
-
|
|
185
|
+
if type(lifecycle) is dict:
|
|
186
|
+
lifecycle = SandboxLifecycle.from_dict(lifecycle)
|
|
187
|
+
assert lifecycle is not None
|
|
188
|
+
sandbox.spec.lifecycle = lifecycle
|
|
189
|
+
elif type(lifecycle) is SandboxLifecycle:
|
|
190
|
+
sandbox.spec.lifecycle = lifecycle
|
|
191
|
+
else:
|
|
192
|
+
raise ValueError(f"Invalid lifecycle type: {type(lifecycle)}")
|
|
170
193
|
else:
|
|
171
194
|
if isinstance(sandbox, dict):
|
|
172
195
|
sandbox = Sandbox.from_dict(sandbox)
|
|
196
|
+
assert isinstance(sandbox, Sandbox)
|
|
197
|
+
|
|
173
198
|
if not sandbox.metadata:
|
|
174
199
|
sandbox.metadata = Metadata(name=default_name)
|
|
175
200
|
if not sandbox.spec:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from ...common.settings import settings
|
|
4
|
+
from ..client.api.system.get_health import sync as get_health
|
|
5
|
+
from ..client.api.system.post_upgrade import sync as post_upgrade
|
|
6
|
+
from ..client.client import Client
|
|
7
|
+
from ..client.models import ErrorResponse, HealthResponse, SuccessResponse, UpgradeRequest
|
|
8
|
+
from ..types import SandboxConfiguration
|
|
9
|
+
from .action import SyncSandboxAction
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SyncSandboxSystem(SyncSandboxAction):
|
|
13
|
+
"""System operations for sandbox including upgrade functionality (sync version)."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, sandbox_config: SandboxConfiguration):
|
|
16
|
+
super().__init__(sandbox_config)
|
|
17
|
+
|
|
18
|
+
def upgrade(
|
|
19
|
+
self,
|
|
20
|
+
version: str | None = None,
|
|
21
|
+
base_url: str | None = None,
|
|
22
|
+
) -> SuccessResponse:
|
|
23
|
+
"""Upgrade the sandbox-api to a new version.
|
|
24
|
+
|
|
25
|
+
Triggers an upgrade of the sandbox-api process. Returns immediately before upgrading.
|
|
26
|
+
The upgrade will: download the specified binary from GitHub releases, validate it, and restart.
|
|
27
|
+
All running processes will be preserved across the upgrade.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
version: Version to upgrade to - "develop" (default), "main", "latest",
|
|
31
|
+
or specific tag like "v1.0.0"
|
|
32
|
+
base_url: Base URL for releases (useful for forks, defaults to
|
|
33
|
+
https://github.com/blaxel-ai/sandbox/releases)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
SuccessResponse with status information
|
|
37
|
+
"""
|
|
38
|
+
request = UpgradeRequest(version=version, base_url=base_url)
|
|
39
|
+
|
|
40
|
+
client = Client(
|
|
41
|
+
base_url=self.url,
|
|
42
|
+
headers={**settings.headers, **self.sandbox_config.headers},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
with client:
|
|
46
|
+
response = post_upgrade(client=client, body=request)
|
|
47
|
+
if response is None:
|
|
48
|
+
raise Exception("Failed to upgrade sandbox")
|
|
49
|
+
if isinstance(response, ErrorResponse):
|
|
50
|
+
raise Exception(f"Upgrade failed: {response.error}")
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
def health(self) -> HealthResponse:
|
|
54
|
+
"""Get health status and system information.
|
|
55
|
+
|
|
56
|
+
Returns health status and system information including upgrade count and binary details.
|
|
57
|
+
Also includes last upgrade attempt status with detailed error information if available.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
HealthResponse with system status information
|
|
61
|
+
"""
|
|
62
|
+
client = Client(
|
|
63
|
+
base_url=self.url,
|
|
64
|
+
headers={**settings.headers, **self.sandbox_config.headers},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
with client:
|
|
68
|
+
response = get_health(client=client)
|
|
69
|
+
if response is None:
|
|
70
|
+
raise Exception("Failed to get health status")
|
|
71
|
+
return response
|