blaxel 0.2.31__py3-none-any.whl → 0.2.31rc120__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 +3 -3
- blaxel/core/agents/__init__.py +6 -13
- blaxel/core/authentication/__init__.py +1 -2
- blaxel/core/authentication/devicemode.py +1 -9
- blaxel/core/authentication/oauth.py +6 -13
- blaxel/core/authentication/types.py +0 -1
- blaxel/core/cache/cache.py +3 -10
- blaxel/core/client/api/agents/list_agent_revisions.py +1 -3
- blaxel/core/client/api/compute/delete_sandbox_preview_token.py +2 -6
- blaxel/core/client/api/compute/start_sandbox.py +1 -3
- blaxel/core/client/api/compute/stop_sandbox.py +1 -3
- blaxel/core/client/api/default/list_sandbox_hub_definitions.py +2 -6
- blaxel/core/client/api/functions/list_function_revisions.py +1 -3
- blaxel/core/client/api/images/cleanup_images.py +1 -3
- blaxel/core/client/api/integrations/list_integration_connections.py +2 -6
- blaxel/core/client/api/invitations/list_all_pending_invitations.py +1 -3
- blaxel/core/client/api/jobs/create_job_execution.py +1 -3
- blaxel/core/client/api/jobs/delete_job_execution.py +1 -3
- blaxel/core/client/api/jobs/get_job_execution.py +1 -3
- blaxel/core/client/api/jobs/list_job_executions.py +2 -6
- blaxel/core/client/api/jobs/list_job_revisions.py +1 -3
- blaxel/core/client/api/locations/list_locations.py +1 -3
- blaxel/core/client/api/models/list_model_revisions.py +1 -3
- blaxel/core/client/api/service_accounts/create_workspace_service_account.py +2 -6
- blaxel/core/client/api/service_accounts/delete_workspace_service_account.py +2 -6
- blaxel/core/client/api/service_accounts/get_workspace_service_accounts.py +1 -3
- blaxel/core/client/api/service_accounts/update_workspace_service_account.py +2 -6
- blaxel/core/client/api/volume_templates/list_volume_templates.py +1 -3
- blaxel/core/client/api/workspaces/accept_workspace_invitation.py +2 -6
- blaxel/core/client/api/workspaces/invite_workspace_user.py +2 -6
- blaxel/core/client/api/workspaces/update_workspace_user_role.py +2 -6
- blaxel/core/client/client.py +19 -5
- blaxel/core/client/models/agent.py +4 -11
- blaxel/core/client/models/agent_spec.py +5 -18
- blaxel/core/client/models/billable_time_metric.py +1 -0
- blaxel/core/client/models/configuration.py +1 -0
- blaxel/core/client/models/core_spec.py +3 -10
- blaxel/core/client/models/core_spec_configurations.py +1 -0
- blaxel/core/client/models/create_job_execution_request.py +1 -0
- blaxel/core/client/models/create_job_execution_response.py +1 -0
- blaxel/core/client/models/custom_domain.py +2 -5
- blaxel/core/client/models/custom_domain_metadata.py +1 -0
- blaxel/core/client/models/custom_domain_spec.py +2 -5
- blaxel/core/client/models/delete_volume_template_version_response_200.py +2 -5
- blaxel/core/client/models/entrypoint.py +1 -0
- blaxel/core/client/models/form.py +2 -5
- blaxel/core/client/models/function.py +4 -11
- blaxel/core/client/models/function_spec.py +4 -13
- blaxel/core/client/models/image.py +2 -5
- blaxel/core/client/models/image_spec.py +1 -0
- blaxel/core/client/models/integration.py +3 -10
- blaxel/core/client/models/integration_connection.py +2 -5
- blaxel/core/client/models/integration_connection_spec.py +1 -0
- blaxel/core/client/models/integration_endpoint.py +2 -5
- blaxel/core/client/models/integration_endpoints.py +2 -0
- blaxel/core/client/models/job.py +4 -11
- blaxel/core/client/models/job_execution.py +2 -5
- blaxel/core/client/models/job_execution_spec.py +1 -0
- blaxel/core/client/models/job_execution_task.py +2 -5
- blaxel/core/client/models/job_metrics.py +2 -5
- blaxel/core/client/models/job_spec.py +4 -13
- blaxel/core/client/models/jobs_network_chart.py +1 -0
- blaxel/core/client/models/jobs_success_failed_chart.py +3 -10
- blaxel/core/client/models/latency_metric.py +2 -5
- blaxel/core/client/models/location_response.py +1 -0
- blaxel/core/client/models/mcp_definition.py +2 -5
- blaxel/core/client/models/metadata.py +1 -0
- blaxel/core/client/models/metrics.py +4 -11
- blaxel/core/client/models/model.py +4 -11
- blaxel/core/client/models/model_spec.py +3 -10
- blaxel/core/client/models/pending_invitation_accept.py +2 -5
- blaxel/core/client/models/pending_invitation_render.py +3 -10
- blaxel/core/client/models/policy.py +2 -5
- blaxel/core/client/models/policy_spec.py +4 -11
- blaxel/core/client/models/preview.py +2 -5
- blaxel/core/client/models/preview_spec.py +1 -0
- blaxel/core/client/models/preview_token.py +2 -5
- blaxel/core/client/models/public_ips.py +1 -0
- blaxel/core/client/models/request_duration_over_time_metrics.py +1 -0
- blaxel/core/client/models/request_total_by_origin_metric.py +7 -16
- blaxel/core/client/models/request_total_metric.py +3 -8
- blaxel/core/client/models/resource_metrics.py +17 -58
- blaxel/core/client/models/runtime.py +1 -0
- blaxel/core/client/models/sandbox.py +4 -11
- blaxel/core/client/models/sandbox_definition.py +1 -0
- blaxel/core/client/models/sandbox_lifecycle.py +1 -0
- blaxel/core/client/models/sandbox_spec.py +6 -21
- blaxel/core/client/models/serverless_config.py +1 -0
- blaxel/core/client/models/start_sandbox.py +2 -5
- blaxel/core/client/models/stop_sandbox.py +2 -5
- blaxel/core/client/models/store_agent.py +1 -0
- blaxel/core/client/models/store_configuration.py +1 -0
- blaxel/core/client/models/template.py +1 -0
- blaxel/core/client/models/time_to_first_token_over_time_metrics.py +2 -3
- blaxel/core/client/models/token_rate_metrics.py +1 -0
- blaxel/core/client/models/trigger.py +1 -0
- blaxel/core/client/models/trigger_configuration.py +1 -0
- blaxel/core/client/models/volume.py +4 -11
- blaxel/core/client/models/volume_template.py +2 -5
- blaxel/core/client/models/workspace.py +2 -5
- blaxel/core/client/response_interceptor.py +1 -3
- blaxel/core/common/autoload.py +11 -9
- blaxel/core/common/env.py +8 -10
- blaxel/core/common/settings.py +2 -4
- blaxel/core/common/webhook.py +1 -0
- blaxel/core/jobs/__init__.py +3 -13
- blaxel/core/mcp/client.py +2 -8
- blaxel/core/mcp/server.py +2 -8
- blaxel/core/models/__init__.py +5 -6
- blaxel/core/sandbox/__init__.py +1 -1
- blaxel/core/sandbox/client/api/codegen/get_codegen_reranking_path.py +2 -6
- blaxel/core/sandbox/client/api/fastapply/put_codegen_fastapply_path.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_multipart_upload_id_abort.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_path.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/delete_filesystem_tree_path.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_content_search_path.py +1 -3
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_find_path.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/get_filesystem_search_path.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/get_watch_filesystem_path.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/post_filesystem_multipart_upload_id_complete.py +2 -6
- blaxel/core/sandbox/client/api/filesystem/put_filesystem_path.py +2 -6
- blaxel/core/sandbox/client/api/process/delete_process_identifier.py +2 -6
- blaxel/core/sandbox/client/api/process/delete_process_identifier_kill.py +2 -6
- blaxel/core/sandbox/client/api/process/get_process.py +1 -3
- blaxel/core/sandbox/client/api/process/get_process_identifier.py +2 -6
- blaxel/core/sandbox/client/api/process/get_process_identifier_logs.py +2 -6
- blaxel/core/sandbox/client/api/process/get_process_identifier_logs_stream.py +2 -6
- blaxel/core/sandbox/client/api/process/post_process.py +2 -6
- blaxel/core/sandbox/client/client.py +1 -3
- blaxel/core/sandbox/client/models/filesystem_multipart_upload_parts.py +1 -3
- blaxel/core/sandbox/default/__init__.py +1 -0
- blaxel/core/sandbox/default/action.py +3 -3
- blaxel/core/sandbox/default/codegen.py +4 -2
- blaxel/core/sandbox/default/filesystem.py +82 -38
- blaxel/core/sandbox/default/interpreter.py +10 -17
- blaxel/core/sandbox/default/preview.py +2 -6
- blaxel/core/sandbox/default/process.py +7 -25
- blaxel/core/sandbox/default/sandbox.py +2 -7
- blaxel/core/sandbox/sync/__init__.py +2 -0
- blaxel/core/sandbox/sync/action.py +3 -2
- blaxel/core/sandbox/sync/codegen.py +5 -1
- blaxel/core/sandbox/sync/filesystem.py +6 -17
- blaxel/core/sandbox/sync/interpreter.py +6 -10
- blaxel/core/sandbox/sync/network.py +2 -0
- blaxel/core/sandbox/sync/preview.py +9 -21
- blaxel/core/sandbox/sync/process.py +8 -32
- blaxel/core/sandbox/sync/sandbox.py +6 -13
- blaxel/core/sandbox/sync/session.py +4 -6
- blaxel/core/sandbox/types.py +1 -2
- blaxel/core/tools/__init__.py +6 -30
- blaxel/core/tools/common.py +1 -1
- blaxel/core/tools/types.py +1 -2
- blaxel/crewai/model.py +5 -20
- blaxel/googleadk/__init__.py +1 -1
- blaxel/googleadk/tools.py +5 -3
- blaxel/langgraph/custom/gemini.py +133 -126
- blaxel/langgraph/model.py +50 -54
- blaxel/langgraph/tools.py +3 -9
- blaxel/llamaindex/custom/cohere.py +16 -25
- blaxel/llamaindex/model.py +57 -44
- blaxel/llamaindex/tools.py +3 -2
- blaxel/pydantic/custom/gemini.py +3 -3
- blaxel/pydantic/tools.py +4 -2
- blaxel/telemetry/exporters.py +3 -10
- blaxel/telemetry/instrumentation/blaxel_langgraph.py +2 -4
- blaxel/telemetry/instrumentation/blaxel_langgraph_gemini.py +5 -22
- blaxel/telemetry/instrumentation/utils.py +3 -3
- blaxel/telemetry/log/log.py +3 -2
- blaxel/telemetry/log/logger.py +15 -21
- blaxel/telemetry/span.py +6 -10
- {blaxel-0.2.31.dist-info → blaxel-0.2.31rc120.dist-info}/METADATA +2 -2
- {blaxel-0.2.31.dist-info → blaxel-0.2.31rc120.dist-info}/RECORD +174 -174
- {blaxel-0.2.31.dist-info → blaxel-0.2.31rc120.dist-info}/WHEEL +0 -0
- {blaxel-0.2.31.dist-info → blaxel-0.2.31rc120.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,9 +17,7 @@ class CodeInterpreter(SandboxInstance):
|
|
|
17
17
|
DEFAULT_PORTS = [
|
|
18
18
|
{"name": "jupyter", "target": 8888, "protocol": "HTTP"},
|
|
19
19
|
]
|
|
20
|
-
DEFAULT_LIFECYCLE = {
|
|
21
|
-
"expirationPolicies": [{"type": "ttl-idle", "value": "30m", "action": "delete"}]
|
|
22
|
-
}
|
|
20
|
+
DEFAULT_LIFECYCLE = {"expirationPolicies": [{"type": "ttl-idle", "value": "30m", "action": "delete"}]}
|
|
23
21
|
|
|
24
22
|
@classmethod
|
|
25
23
|
async def get(cls, sandbox_name: str) -> CodeInterpreter:
|
|
@@ -150,15 +148,11 @@ class CodeInterpreter(SandboxInstance):
|
|
|
150
148
|
elif data_type == "stdout":
|
|
151
149
|
execution.logs.stdout.append(data["text"])
|
|
152
150
|
if on_stdout:
|
|
153
|
-
return on_stdout(
|
|
154
|
-
CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), False)
|
|
155
|
-
)
|
|
151
|
+
return on_stdout(CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), False))
|
|
156
152
|
elif data_type == "stderr":
|
|
157
153
|
execution.logs.stderr.append(data["text"])
|
|
158
154
|
if on_stderr:
|
|
159
|
-
return on_stderr(
|
|
160
|
-
CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), True)
|
|
161
|
-
)
|
|
155
|
+
return on_stderr(CodeInterpreter.OutputMessage(data["text"], data.get("timestamp"), True))
|
|
162
156
|
elif data_type == "error":
|
|
163
157
|
execution.error = CodeInterpreter.ExecutionError(
|
|
164
158
|
data.get("name", ""), data.get("value"), data.get("traceback")
|
|
@@ -206,10 +200,7 @@ class CodeInterpreter(SandboxInstance):
|
|
|
206
200
|
|
|
207
201
|
client = self.process.get_client()
|
|
208
202
|
timeout_cfg = httpx.Timeout(
|
|
209
|
-
connect=connect_timeout,
|
|
210
|
-
read=read_timeout,
|
|
211
|
-
write=write_timeout,
|
|
212
|
-
pool=pool_timeout,
|
|
203
|
+
connect=connect_timeout, read=read_timeout, write=write_timeout, pool=pool_timeout
|
|
213
204
|
)
|
|
214
205
|
async with client.stream(
|
|
215
206
|
"POST",
|
|
@@ -252,7 +243,7 @@ class CodeInterpreter(SandboxInstance):
|
|
|
252
243
|
on_error=on_error,
|
|
253
244
|
)
|
|
254
245
|
except json.JSONDecodeError:
|
|
255
|
-
|
|
246
|
+
# Fallback: treat as stdout text-only message
|
|
256
247
|
execution.logs.stdout.append(decoded)
|
|
257
248
|
if on_stdout:
|
|
258
249
|
on_stdout(CodeInterpreter.OutputMessage(decoded, None, False))
|
|
@@ -280,10 +271,10 @@ class CodeInterpreter(SandboxInstance):
|
|
|
280
271
|
try:
|
|
281
272
|
# Always read response body first
|
|
282
273
|
body_bytes = await response.aread()
|
|
283
|
-
|
|
274
|
+
|
|
284
275
|
if response.status_code >= 400:
|
|
285
276
|
try:
|
|
286
|
-
body_text = body_bytes.decode(
|
|
277
|
+
body_text = body_bytes.decode('utf-8', errors='ignore')
|
|
287
278
|
except Exception:
|
|
288
279
|
body_text = "<unavailable>"
|
|
289
280
|
method = getattr(response.request, "method", "UNKNOWN")
|
|
@@ -296,8 +287,10 @@ class CodeInterpreter(SandboxInstance):
|
|
|
296
287
|
)
|
|
297
288
|
self.logger.debug(details)
|
|
298
289
|
raise RuntimeError(details)
|
|
299
|
-
|
|
290
|
+
|
|
300
291
|
data = json.loads(body_bytes)
|
|
301
292
|
return CodeInterpreter.Context.from_json(data)
|
|
302
293
|
finally:
|
|
303
294
|
await response.aclose()
|
|
295
|
+
|
|
296
|
+
|
|
@@ -15,15 +15,11 @@ from ...client.api.compute.delete_sandbox_preview import (
|
|
|
15
15
|
from ...client.api.compute.delete_sandbox_preview_token import (
|
|
16
16
|
asyncio as delete_sandbox_preview_token,
|
|
17
17
|
)
|
|
18
|
-
from ...client.api.compute.get_sandbox_preview import
|
|
19
|
-
asyncio as get_sandbox_preview,
|
|
20
|
-
)
|
|
18
|
+
from ...client.api.compute.get_sandbox_preview import asyncio as get_sandbox_preview
|
|
21
19
|
from ...client.api.compute.list_sandbox_preview_tokens import (
|
|
22
20
|
asyncio as list_sandbox_preview_tokens,
|
|
23
21
|
)
|
|
24
|
-
from ...client.api.compute.list_sandbox_previews import
|
|
25
|
-
asyncio as list_sandbox_previews,
|
|
26
|
-
)
|
|
22
|
+
from ...client.api.compute.list_sandbox_previews import asyncio as list_sandbox_previews
|
|
27
23
|
from ...client.client import client
|
|
28
24
|
from ...client.models import (
|
|
29
25
|
Preview,
|
|
@@ -6,11 +6,7 @@ import httpx
|
|
|
6
6
|
from ...common.settings import settings
|
|
7
7
|
from ..client.models import ProcessResponse, SuccessResponse
|
|
8
8
|
from ..client.models.process_request import ProcessRequest
|
|
9
|
-
from ..types import
|
|
10
|
-
ProcessRequestWithLog,
|
|
11
|
-
ProcessResponseWithLog,
|
|
12
|
-
SandboxConfiguration,
|
|
13
|
-
)
|
|
9
|
+
from ..types import ProcessRequestWithLog, ProcessResponseWithLog, SandboxConfiguration
|
|
14
10
|
from .action import SandboxAction
|
|
15
11
|
|
|
16
12
|
|
|
@@ -19,9 +15,7 @@ class SandboxProcess(SandboxAction):
|
|
|
19
15
|
super().__init__(sandbox_config)
|
|
20
16
|
|
|
21
17
|
def stream_logs(
|
|
22
|
-
self,
|
|
23
|
-
process_name: str,
|
|
24
|
-
options: Dict[str, Callable[[str], None]] | None = None,
|
|
18
|
+
self, process_name: str, options: Dict[str, Callable[[str], None]] | None = None
|
|
25
19
|
) -> Dict[str, Callable[[], None]]:
|
|
26
20
|
"""Stream logs from a process with automatic reconnection and deduplication."""
|
|
27
21
|
if options is None:
|
|
@@ -117,9 +111,7 @@ class SandboxProcess(SandboxAction):
|
|
|
117
111
|
return {"close": close}
|
|
118
112
|
|
|
119
113
|
def _stream_logs(
|
|
120
|
-
self,
|
|
121
|
-
identifier: str,
|
|
122
|
-
options: Dict[str, Callable[[str], None]] | None = None,
|
|
114
|
+
self, identifier: str, options: Dict[str, Callable[[str], None]] | None = None
|
|
123
115
|
) -> Dict[str, Callable[[], None]]:
|
|
124
116
|
"""Private method to stream logs from a process with callbacks for different output types."""
|
|
125
117
|
if options is None:
|
|
@@ -183,8 +175,7 @@ class SandboxProcess(SandboxAction):
|
|
|
183
175
|
return {"close": close}
|
|
184
176
|
|
|
185
177
|
async def exec(
|
|
186
|
-
self,
|
|
187
|
-
process: Union[ProcessRequest, ProcessRequestWithLog, Dict[str, Any]],
|
|
178
|
+
self, process: Union[ProcessRequest, ProcessRequestWithLog, Dict[str, Any]]
|
|
188
179
|
) -> Union[ProcessResponse, ProcessResponseWithLog]:
|
|
189
180
|
"""Execute a process in the sandbox."""
|
|
190
181
|
on_log = None
|
|
@@ -209,14 +200,13 @@ class SandboxProcess(SandboxAction):
|
|
|
209
200
|
# Always start process without wait_for_completion to avoid server-side blocking
|
|
210
201
|
if should_wait_for_completion and on_log is not None:
|
|
211
202
|
process.wait_for_completion = False
|
|
212
|
-
|
|
203
|
+
|
|
213
204
|
client = self.get_client()
|
|
214
205
|
response = await client.post("/process", json=process.to_dict())
|
|
215
206
|
try:
|
|
216
207
|
content_bytes = await response.aread()
|
|
217
208
|
self.handle_response_error(response)
|
|
218
209
|
import json
|
|
219
|
-
|
|
220
210
|
response_data = json.loads(content_bytes) if content_bytes else None
|
|
221
211
|
result = ProcessResponse.from_dict(response_data)
|
|
222
212
|
finally:
|
|
@@ -237,8 +227,7 @@ class SandboxProcess(SandboxAction):
|
|
|
237
227
|
if on_log is not None:
|
|
238
228
|
stream_control = self._stream_logs(result.pid, {"on_log": on_log})
|
|
239
229
|
return ProcessResponseWithLog(
|
|
240
|
-
result,
|
|
241
|
-
lambda: stream_control["close"]() if stream_control else None,
|
|
230
|
+
result, lambda: stream_control["close"]() if stream_control else None
|
|
242
231
|
)
|
|
243
232
|
|
|
244
233
|
return result
|
|
@@ -266,7 +255,6 @@ class SandboxProcess(SandboxAction):
|
|
|
266
255
|
|
|
267
256
|
async def get(self, identifier: str) -> ProcessResponse:
|
|
268
257
|
import json
|
|
269
|
-
|
|
270
258
|
client = self.get_client()
|
|
271
259
|
response = await client.get(f"/process/{identifier}")
|
|
272
260
|
try:
|
|
@@ -278,7 +266,6 @@ class SandboxProcess(SandboxAction):
|
|
|
278
266
|
|
|
279
267
|
async def list(self) -> list[ProcessResponse]:
|
|
280
268
|
import json
|
|
281
|
-
|
|
282
269
|
client = self.get_client()
|
|
283
270
|
response = await client.get("/process")
|
|
284
271
|
try:
|
|
@@ -290,7 +277,6 @@ class SandboxProcess(SandboxAction):
|
|
|
290
277
|
|
|
291
278
|
async def stop(self, identifier: str) -> SuccessResponse:
|
|
292
279
|
import json
|
|
293
|
-
|
|
294
280
|
client = self.get_client()
|
|
295
281
|
response = await client.delete(f"/process/{identifier}")
|
|
296
282
|
try:
|
|
@@ -302,7 +288,6 @@ class SandboxProcess(SandboxAction):
|
|
|
302
288
|
|
|
303
289
|
async def kill(self, identifier: str) -> SuccessResponse:
|
|
304
290
|
import json
|
|
305
|
-
|
|
306
291
|
client = self.get_client()
|
|
307
292
|
response = await client.delete(f"/process/{identifier}/kill")
|
|
308
293
|
try:
|
|
@@ -313,12 +298,9 @@ class SandboxProcess(SandboxAction):
|
|
|
313
298
|
await response.aclose()
|
|
314
299
|
|
|
315
300
|
async def logs(
|
|
316
|
-
self,
|
|
317
|
-
identifier: str,
|
|
318
|
-
log_type: Literal["stdout", "stderr", "all"] = "all",
|
|
301
|
+
self, identifier: str, log_type: Literal["stdout", "stderr", "all"] = "all"
|
|
319
302
|
) -> str:
|
|
320
303
|
import json
|
|
321
|
-
|
|
322
304
|
client = self.get_client()
|
|
323
305
|
response = await client.get(f"/process/{identifier}/logs")
|
|
324
306
|
try:
|
|
@@ -105,8 +105,7 @@ class SandboxInstance:
|
|
|
105
105
|
or "expires" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
106
106
|
or "region" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
107
107
|
or "lifecycle" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
108
|
-
or "snapshot_enabled"
|
|
109
|
-
in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
108
|
+
or "snapshot_enabled" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
110
109
|
)
|
|
111
110
|
)
|
|
112
111
|
):
|
|
@@ -133,11 +132,7 @@ class SandboxInstance:
|
|
|
133
132
|
metadata=Metadata(name=name),
|
|
134
133
|
spec=SandboxSpec(
|
|
135
134
|
runtime=Runtime(
|
|
136
|
-
image=image,
|
|
137
|
-
memory=memory,
|
|
138
|
-
ports=ports,
|
|
139
|
-
envs=envs,
|
|
140
|
-
generation="mk3",
|
|
135
|
+
image=image, memory=memory, ports=ports, envs=envs, generation="mk3"
|
|
141
136
|
),
|
|
142
137
|
volumes=volumes,
|
|
143
138
|
),
|
|
@@ -51,8 +51,7 @@ class SyncSandboxAction:
|
|
|
51
51
|
def get_client(self) -> httpx.Client:
|
|
52
52
|
if self.sandbox_config.force_url:
|
|
53
53
|
return httpx.Client(
|
|
54
|
-
base_url=self.sandbox_config.force_url,
|
|
55
|
-
headers=self.sandbox_config.headers,
|
|
54
|
+
base_url=self.sandbox_config.force_url, headers=self.sandbox_config.headers
|
|
56
55
|
)
|
|
57
56
|
return httpx.Client(
|
|
58
57
|
base_url=self.url,
|
|
@@ -62,3 +61,5 @@ class SyncSandboxAction:
|
|
|
62
61
|
def handle_response_error(self, response: httpx.Response):
|
|
63
62
|
if not response.is_success:
|
|
64
63
|
raise ResponseError(response)
|
|
64
|
+
|
|
65
|
+
|
|
@@ -20,7 +20,9 @@ class SyncSandboxCodegen(SyncSandboxAction):
|
|
|
20
20
|
def __init__(self, sandbox_config: SandboxConfiguration):
|
|
21
21
|
super().__init__(sandbox_config)
|
|
22
22
|
|
|
23
|
-
def fastapply(
|
|
23
|
+
def fastapply(
|
|
24
|
+
self, path: str, code_edit: str, model: str | None = None
|
|
25
|
+
) -> ApplyEditResponse:
|
|
24
26
|
body = ApplyEditRequest(code_edit=code_edit, model=model)
|
|
25
27
|
client = Client(
|
|
26
28
|
base_url=self.url,
|
|
@@ -64,3 +66,5 @@ class SyncSandboxCodegen(SyncSandboxAction):
|
|
|
64
66
|
if isinstance(response, ErrorResponse):
|
|
65
67
|
raise Exception(f"Reranking failed: {response}")
|
|
66
68
|
return response
|
|
69
|
+
|
|
70
|
+
|
|
@@ -61,11 +61,7 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
61
61
|
return self._upload_with_multipart(path, content, "0644")
|
|
62
62
|
binary_file = io.BytesIO(content)
|
|
63
63
|
files = {
|
|
64
|
-
"file": (
|
|
65
|
-
"binary-file.bin",
|
|
66
|
-
binary_file,
|
|
67
|
-
"application/octet-stream",
|
|
68
|
-
),
|
|
64
|
+
"file": ("binary-file.bin", binary_file, "application/octet-stream"),
|
|
69
65
|
}
|
|
70
66
|
data = {"permissions": "0644", "path": path}
|
|
71
67
|
url = f"{self.url}/filesystem/{path}"
|
|
@@ -199,10 +195,7 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
199
195
|
name=file_event_data.get("name", ""),
|
|
200
196
|
content=file_event_data.get("content"),
|
|
201
197
|
)
|
|
202
|
-
if options.get("with_content") and file_event.op in [
|
|
203
|
-
"CREATE",
|
|
204
|
-
"WRITE",
|
|
205
|
-
]:
|
|
198
|
+
if options.get("with_content") and file_event.op in ["CREATE", "WRITE"]:
|
|
206
199
|
try:
|
|
207
200
|
file_path = file_event.path
|
|
208
201
|
if file_path.endswith("/"):
|
|
@@ -251,9 +244,7 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
251
244
|
self.handle_response_error(response)
|
|
252
245
|
return response.json()
|
|
253
246
|
|
|
254
|
-
def _complete_multipart_upload(
|
|
255
|
-
self, upload_id: str, parts: List[Dict[str, Any]]
|
|
256
|
-
) -> SuccessResponse:
|
|
247
|
+
def _complete_multipart_upload(self, upload_id: str, parts: List[Dict[str, Any]]) -> SuccessResponse:
|
|
257
248
|
url = f"{self.url}/filesystem-multipart/{upload_id}/complete"
|
|
258
249
|
headers = {**settings.headers, **self.sandbox_config.headers}
|
|
259
250
|
body = {"parts": parts}
|
|
@@ -270,9 +261,7 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
270
261
|
if not response.is_success:
|
|
271
262
|
logger.warning(f"Failed to abort multipart upload: {response.status_code}")
|
|
272
263
|
|
|
273
|
-
def _upload_with_multipart(
|
|
274
|
-
self, path: str, data: bytes, permissions: str = "0644"
|
|
275
|
-
) -> SuccessResponse:
|
|
264
|
+
def _upload_with_multipart(self, path: str, data: bytes, permissions: str = "0644") -> SuccessResponse:
|
|
276
265
|
init_response = self._initiate_multipart_upload(path, permissions)
|
|
277
266
|
upload_id = init_response.get("uploadId")
|
|
278
267
|
if not upload_id:
|
|
@@ -285,10 +274,8 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
285
274
|
for i in range(0, num_parts, MAX_PARALLEL_UPLOADS):
|
|
286
275
|
threads = []
|
|
287
276
|
results: Dict[int, Dict[str, Any]] = {}
|
|
288
|
-
|
|
289
277
|
def make_upload(part_number: int, chunk: bytes):
|
|
290
278
|
results[part_number] = self._upload_part(upload_id, part_number, chunk)
|
|
291
|
-
|
|
292
279
|
for j in range(MAX_PARALLEL_UPLOADS):
|
|
293
280
|
if i + j >= num_parts:
|
|
294
281
|
break
|
|
@@ -311,3 +298,5 @@ class SyncSandboxFileSystem(SyncSandboxAction):
|
|
|
311
298
|
except Exception as abort_error:
|
|
312
299
|
logger.warning(f"Failed to abort multipart upload: {abort_error}")
|
|
313
300
|
raise error
|
|
301
|
+
|
|
302
|
+
|
|
@@ -15,9 +15,7 @@ class SyncCodeInterpreter(SyncSandboxInstance):
|
|
|
15
15
|
DEFAULT_PORTS = [
|
|
16
16
|
{"name": "jupyter", "target": 8888, "protocol": "HTTP"},
|
|
17
17
|
]
|
|
18
|
-
DEFAULT_LIFECYCLE = {
|
|
19
|
-
"expirationPolicies": [{"type": "ttl-idle", "value": "30m", "action": "delete"}]
|
|
20
|
-
}
|
|
18
|
+
DEFAULT_LIFECYCLE = {"expirationPolicies": [{ "type": "ttl-idle", "value": "30m", "action": "delete" }]}
|
|
21
19
|
|
|
22
20
|
@classmethod
|
|
23
21
|
def get(cls, sandbox_name: str) -> "SyncCodeInterpreter":
|
|
@@ -223,10 +221,7 @@ class SyncCodeInterpreter(SyncSandboxInstance):
|
|
|
223
221
|
# Use the process client to inherit base_url and headers
|
|
224
222
|
with self.process.get_client() as client:
|
|
225
223
|
timeout_cfg = httpx.Timeout(
|
|
226
|
-
connect=connect_timeout,
|
|
227
|
-
read=read_timeout,
|
|
228
|
-
write=write_timeout,
|
|
229
|
-
pool=pool_timeout,
|
|
224
|
+
connect=connect_timeout, read=read_timeout, write=write_timeout, pool=pool_timeout
|
|
230
225
|
)
|
|
231
226
|
with client.stream(
|
|
232
227
|
"POST",
|
|
@@ -243,9 +238,7 @@ class SyncCodeInterpreter(SyncSandboxInstance):
|
|
|
243
238
|
if not line:
|
|
244
239
|
continue
|
|
245
240
|
try:
|
|
246
|
-
decoded = (
|
|
247
|
-
line.decode() if isinstance(line, bytes | bytearray) else str(line)
|
|
248
|
-
)
|
|
241
|
+
decoded = line.decode() if isinstance(line, bytes | bytearray) else str(line)
|
|
249
242
|
except Exception:
|
|
250
243
|
decoded = str(line)
|
|
251
244
|
try:
|
|
@@ -277,6 +270,7 @@ class SyncCodeInterpreter(SyncSandboxInstance):
|
|
|
277
270
|
if cwd:
|
|
278
271
|
data["cwd"] = cwd
|
|
279
272
|
|
|
273
|
+
|
|
280
274
|
with self.process.get_client() as client:
|
|
281
275
|
response = client.post(
|
|
282
276
|
"/port/8888/contexts",
|
|
@@ -289,3 +283,5 @@ class SyncCodeInterpreter(SyncSandboxInstance):
|
|
|
289
283
|
raise RuntimeError(details)
|
|
290
284
|
data = response.json()
|
|
291
285
|
return SyncCodeInterpreter.Context.from_json(data)
|
|
286
|
+
|
|
287
|
+
|
|
@@ -3,27 +3,13 @@ from datetime import datetime
|
|
|
3
3
|
from typing import Any, Dict, List, Union
|
|
4
4
|
|
|
5
5
|
from ...client import errors
|
|
6
|
-
from ...client.api.compute.create_sandbox_preview import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from ...client.api.compute.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from ...client.api.compute.
|
|
13
|
-
sync as delete_sandbox_preview,
|
|
14
|
-
)
|
|
15
|
-
from ...client.api.compute.delete_sandbox_preview_token import (
|
|
16
|
-
sync as delete_sandbox_preview_token,
|
|
17
|
-
)
|
|
18
|
-
from ...client.api.compute.get_sandbox_preview import (
|
|
19
|
-
sync as get_sandbox_preview,
|
|
20
|
-
)
|
|
21
|
-
from ...client.api.compute.list_sandbox_preview_tokens import (
|
|
22
|
-
sync as list_sandbox_preview_tokens,
|
|
23
|
-
)
|
|
24
|
-
from ...client.api.compute.list_sandbox_previews import (
|
|
25
|
-
sync as list_sandbox_previews,
|
|
26
|
-
)
|
|
6
|
+
from ...client.api.compute.create_sandbox_preview import sync as create_sandbox_preview
|
|
7
|
+
from ...client.api.compute.create_sandbox_preview_token import sync as create_sandbox_preview_token
|
|
8
|
+
from ...client.api.compute.delete_sandbox_preview import sync as delete_sandbox_preview
|
|
9
|
+
from ...client.api.compute.delete_sandbox_preview_token import sync as delete_sandbox_preview_token
|
|
10
|
+
from ...client.api.compute.get_sandbox_preview import sync as get_sandbox_preview
|
|
11
|
+
from ...client.api.compute.list_sandbox_preview_tokens import sync as list_sandbox_preview_tokens
|
|
12
|
+
from ...client.api.compute.list_sandbox_previews import sync as list_sandbox_previews
|
|
27
13
|
from ...client.client import client
|
|
28
14
|
from ...client.models import (
|
|
29
15
|
Preview,
|
|
@@ -169,3 +155,5 @@ def to_utc_z(dt: datetime) -> str:
|
|
|
169
155
|
elif "T" in iso_string and not iso_string.endswith("Z"):
|
|
170
156
|
return iso_string + "Z"
|
|
171
157
|
return iso_string
|
|
158
|
+
|
|
159
|
+
|
|
@@ -7,11 +7,7 @@ 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
|
|
11
|
-
ProcessRequestWithLog,
|
|
12
|
-
ProcessResponseWithLog,
|
|
13
|
-
SandboxConfiguration,
|
|
14
|
-
)
|
|
10
|
+
from ..types import ProcessRequestWithLog, ProcessResponseWithLog, SandboxConfiguration
|
|
15
11
|
from .action import SyncSandboxAction
|
|
16
12
|
|
|
17
13
|
|
|
@@ -20,9 +16,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
20
16
|
super().__init__(sandbox_config)
|
|
21
17
|
|
|
22
18
|
def stream_logs(
|
|
23
|
-
self,
|
|
24
|
-
process_name: str,
|
|
25
|
-
options: Dict[str, Callable[[str], None]] | None = None,
|
|
19
|
+
self, process_name: str, options: Dict[str, Callable[[str], None]] | None = None
|
|
26
20
|
) -> Dict[str, Callable[[], None]]:
|
|
27
21
|
if options is None:
|
|
28
22
|
options = {}
|
|
@@ -37,7 +31,6 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
37
31
|
def start_stream():
|
|
38
32
|
nonlocal current_close
|
|
39
33
|
log_counter = [0]
|
|
40
|
-
|
|
41
34
|
def make_dedup(cb_key: str):
|
|
42
35
|
def inner(content: str):
|
|
43
36
|
key = f"{log_counter[0]}:{content}"
|
|
@@ -46,9 +39,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
46
39
|
seen_logs.add(key)
|
|
47
40
|
if options.get(cb_key):
|
|
48
41
|
options[cb_key](content)
|
|
49
|
-
|
|
50
42
|
return inner
|
|
51
|
-
|
|
52
43
|
wrapped_options: Dict[str, Callable[[str], None]] = {}
|
|
53
44
|
if "on_log" in options:
|
|
54
45
|
wrapped_options["on_log"] = make_dedup("on_log")
|
|
@@ -59,11 +50,9 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
59
50
|
if current_close["fn"]:
|
|
60
51
|
current_close["fn"]()
|
|
61
52
|
current_close["fn"] = self._stream_logs(process_name, wrapped_options)["close"]
|
|
62
|
-
|
|
63
53
|
def schedule():
|
|
64
54
|
if is_running.is_set():
|
|
65
55
|
start_stream()
|
|
66
|
-
|
|
67
56
|
with timer_lock:
|
|
68
57
|
if reconnect_timer["t"]:
|
|
69
58
|
reconnect_timer["t"].cancel()
|
|
@@ -71,9 +60,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
71
60
|
reconnect_timer["t"] = t
|
|
72
61
|
t.daemon = True
|
|
73
62
|
t.start()
|
|
74
|
-
|
|
75
63
|
start_stream()
|
|
76
|
-
|
|
77
64
|
def close():
|
|
78
65
|
is_running.clear()
|
|
79
66
|
with timer_lock:
|
|
@@ -84,18 +71,14 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
84
71
|
current_close["fn"]()
|
|
85
72
|
current_close["fn"] = None
|
|
86
73
|
seen_logs.clear()
|
|
87
|
-
|
|
88
74
|
return {"close": close}
|
|
89
75
|
|
|
90
76
|
def _stream_logs(
|
|
91
|
-
self,
|
|
92
|
-
identifier: str,
|
|
93
|
-
options: Dict[str, Callable[[str], None]] | None = None,
|
|
77
|
+
self, identifier: str, options: Dict[str, Callable[[str], None]] | None = None
|
|
94
78
|
) -> Dict[str, Callable[[], None]]:
|
|
95
79
|
if options is None:
|
|
96
80
|
options = {}
|
|
97
81
|
closed = threading.Event()
|
|
98
|
-
|
|
99
82
|
def run():
|
|
100
83
|
url = f"{self.url}/process/{identifier}/logs/stream"
|
|
101
84
|
headers = {**settings.headers, **self.sandbox_config.headers}
|
|
@@ -133,18 +116,14 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
133
116
|
# Ignore on close
|
|
134
117
|
if not closed.is_set():
|
|
135
118
|
raise e
|
|
136
|
-
|
|
137
119
|
thread = threading.Thread(target=run, daemon=True)
|
|
138
120
|
thread.start()
|
|
139
|
-
|
|
140
121
|
def close():
|
|
141
122
|
closed.set()
|
|
142
|
-
|
|
143
123
|
return {"close": close}
|
|
144
124
|
|
|
145
125
|
def exec(
|
|
146
|
-
self,
|
|
147
|
-
process: Union[ProcessRequest, ProcessRequestWithLog, Dict[str, Any]],
|
|
126
|
+
self, process: Union[ProcessRequest, ProcessRequestWithLog, Dict[str, Any]]
|
|
148
127
|
) -> Union[ProcessResponse, ProcessResponseWithLog]:
|
|
149
128
|
on_log = None
|
|
150
129
|
if isinstance(process, ProcessRequestWithLog):
|
|
@@ -185,8 +164,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
185
164
|
if on_log is not None:
|
|
186
165
|
stream_control = self._stream_logs(result.pid, {"on_log": on_log})
|
|
187
166
|
return ProcessResponseWithLog(
|
|
188
|
-
result,
|
|
189
|
-
lambda: stream_control["close"]() if stream_control else None,
|
|
167
|
+
result, lambda: stream_control["close"]() if stream_control else None
|
|
190
168
|
)
|
|
191
169
|
return result
|
|
192
170
|
|
|
@@ -229,11 +207,7 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
229
207
|
self.handle_response_error(response)
|
|
230
208
|
return SuccessResponse.from_dict(response.json())
|
|
231
209
|
|
|
232
|
-
def logs(
|
|
233
|
-
self,
|
|
234
|
-
identifier: str,
|
|
235
|
-
log_type: Literal["stdout", "stderr", "all"] = "all",
|
|
236
|
-
) -> str:
|
|
210
|
+
def logs(self, identifier: str, log_type: Literal["stdout", "stderr", "all"] = "all") -> str:
|
|
237
211
|
with self.get_client() as client_instance:
|
|
238
212
|
response = client_instance.get(f"/process/{identifier}/logs")
|
|
239
213
|
self.handle_response_error(response)
|
|
@@ -245,3 +219,5 @@ class SyncSandboxProcess(SyncSandboxAction):
|
|
|
245
219
|
elif log_type == "stderr":
|
|
246
220
|
return data.get("stderr", "")
|
|
247
221
|
raise Exception("Unsupported log type")
|
|
222
|
+
|
|
223
|
+
|
|
@@ -100,8 +100,7 @@ class SyncSandboxInstance:
|
|
|
100
100
|
or "expires" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
101
101
|
or "region" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
102
102
|
or "lifecycle" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
103
|
-
or "snapshot_enabled"
|
|
104
|
-
in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
103
|
+
or "snapshot_enabled" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
|
|
105
104
|
)
|
|
106
105
|
)
|
|
107
106
|
):
|
|
@@ -123,11 +122,7 @@ class SyncSandboxInstance:
|
|
|
123
122
|
metadata=Metadata(name=name),
|
|
124
123
|
spec=SandboxSpec(
|
|
125
124
|
runtime=Runtime(
|
|
126
|
-
image=image,
|
|
127
|
-
memory=memory,
|
|
128
|
-
ports=ports,
|
|
129
|
-
envs=envs,
|
|
130
|
-
generation="mk3",
|
|
125
|
+
image=image, memory=memory, ports=ports, envs=envs, generation="mk3"
|
|
131
126
|
),
|
|
132
127
|
volumes=volumes,
|
|
133
128
|
),
|
|
@@ -188,9 +183,7 @@ class SyncSandboxInstance:
|
|
|
188
183
|
return response
|
|
189
184
|
|
|
190
185
|
@classmethod
|
|
191
|
-
def update_metadata(
|
|
192
|
-
cls, sandbox_name: str, metadata: SandboxUpdateMetadata
|
|
193
|
-
) -> "SyncSandboxInstance":
|
|
186
|
+
def update_metadata(cls, sandbox_name: str, metadata: SandboxUpdateMetadata) -> "SyncSandboxInstance":
|
|
194
187
|
sandbox_instance = cls.get(sandbox_name)
|
|
195
188
|
sandbox = sandbox_instance.sandbox
|
|
196
189
|
updated_sandbox = Sandbox.from_dict(sandbox.to_dict())
|
|
@@ -243,9 +236,7 @@ class SyncSandboxInstance:
|
|
|
243
236
|
raise e
|
|
244
237
|
|
|
245
238
|
@classmethod
|
|
246
|
-
def from_session(
|
|
247
|
-
cls, session: Union[SessionWithToken, Dict[str, Any]]
|
|
248
|
-
) -> "SyncSandboxInstance":
|
|
239
|
+
def from_session(cls, session: Union[SessionWithToken, Dict[str, Any]]) -> "SyncSandboxInstance":
|
|
249
240
|
if isinstance(session, dict):
|
|
250
241
|
session = SessionWithToken.from_dict(session)
|
|
251
242
|
sandbox_name = session.name.split("-")[0] if "-" in session.name else session.name
|
|
@@ -256,3 +247,5 @@ class SyncSandboxInstance:
|
|
|
256
247
|
headers={"X-Blaxel-Preview-Token": session.token},
|
|
257
248
|
params={"bl_preview_token": session.token},
|
|
258
249
|
)
|
|
250
|
+
|
|
251
|
+
|
|
@@ -22,9 +22,7 @@ class SyncSandboxSessions:
|
|
|
22
22
|
def sandbox_name(self) -> str:
|
|
23
23
|
return self.sandbox_config.metadata.name if self.sandbox_config.metadata else ""
|
|
24
24
|
|
|
25
|
-
def create(
|
|
26
|
-
self, options: Union[SessionCreateOptions, Dict[str, Any]] | None = None
|
|
27
|
-
) -> SessionWithToken:
|
|
25
|
+
def create(self, options: Union[SessionCreateOptions, Dict[str, Any]] | None = None) -> SessionWithToken:
|
|
28
26
|
if options is None:
|
|
29
27
|
options = SessionCreateOptions()
|
|
30
28
|
elif isinstance(options, dict):
|
|
@@ -114,9 +112,9 @@ class SyncSandboxSessions:
|
|
|
114
112
|
return delete_sandbox_preview.sync(self.sandbox_name, name, client=client)
|
|
115
113
|
|
|
116
114
|
def get_token(self, preview_name: str):
|
|
117
|
-
tokens_response = list_sandbox_preview_tokens.sync(
|
|
118
|
-
self.sandbox_name, preview_name, client=client
|
|
119
|
-
)
|
|
115
|
+
tokens_response = list_sandbox_preview_tokens.sync(self.sandbox_name, preview_name, client=client)
|
|
120
116
|
if not tokens_response:
|
|
121
117
|
return None
|
|
122
118
|
return tokens_response[0]
|
|
119
|
+
|
|
120
|
+
|
blaxel/core/sandbox/types.py
CHANGED
|
@@ -148,8 +148,7 @@ class SandboxCreateConfiguration:
|
|
|
148
148
|
memory: int | None = None,
|
|
149
149
|
ports: Union[List[Port], List[Dict[str, Any]]] | None = None,
|
|
150
150
|
envs: List[Dict[str, str]] | None = None,
|
|
151
|
-
volumes: Union[List[VolumeBinding], List[VolumeAttachment], List[Dict[str, Any]]]
|
|
152
|
-
| None = None,
|
|
151
|
+
volumes: Union[List[VolumeBinding], List[VolumeAttachment], List[Dict[str, Any]]] | None = None,
|
|
153
152
|
ttl: str | None = None,
|
|
154
153
|
expires: datetime | None = None,
|
|
155
154
|
region: str | None = None,
|