blaxel 0.2.33__py3-none-any.whl → 0.2.35__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.
Files changed (278) hide show
  1. blaxel/__init__.py +2 -2
  2. blaxel/core/__init__.py +2 -1
  3. blaxel/core/client/api/agents/create_agent.py +64 -19
  4. blaxel/core/client/api/agents/delete_agent.py +44 -15
  5. blaxel/core/client/api/agents/get_agent.py +43 -14
  6. blaxel/core/client/api/agents/list_agents.py +40 -11
  7. blaxel/core/client/api/agents/update_agent.py +60 -19
  8. blaxel/core/client/api/compute/create_sandbox.py +60 -23
  9. blaxel/core/client/api/compute/delete_sandbox.py +40 -19
  10. blaxel/core/client/api/compute/get_sandbox.py +39 -18
  11. blaxel/core/client/api/compute/list_sandboxes.py +40 -19
  12. blaxel/core/client/api/compute/update_sandbox.py +56 -23
  13. blaxel/core/client/api/configurations/get_configuration.py +16 -4
  14. blaxel/core/client/api/customdomains/create_custom_domain.py +12 -0
  15. blaxel/core/client/api/customdomains/list_custom_domains.py +16 -4
  16. blaxel/core/client/api/default/get_template.py +8 -4
  17. blaxel/core/client/api/functions/create_function.py +62 -19
  18. blaxel/core/client/api/functions/delete_function.py +46 -15
  19. blaxel/core/client/api/functions/get_function.py +45 -14
  20. blaxel/core/client/api/functions/list_functions.py +44 -15
  21. blaxel/core/client/api/functions/update_function.py +62 -19
  22. blaxel/core/client/api/images/cleanup_images.py +12 -12
  23. blaxel/core/client/api/images/delete_image.py +12 -8
  24. blaxel/core/client/api/images/delete_image_tag.py +12 -8
  25. blaxel/core/client/api/images/get_image.py +12 -8
  26. blaxel/core/client/api/images/list_images.py +12 -8
  27. blaxel/core/client/api/integrations/create_integration_connection.py +56 -23
  28. blaxel/core/client/api/integrations/delete_integration_connection.py +48 -19
  29. blaxel/core/client/api/integrations/get_integration.py +12 -8
  30. blaxel/core/client/api/integrations/get_integration_connection.py +44 -19
  31. blaxel/core/client/api/integrations/get_integration_connection_model.py +2 -2
  32. blaxel/core/client/api/integrations/list_integration_connections.py +36 -19
  33. blaxel/core/client/api/integrations/update_integration_connection.py +52 -19
  34. blaxel/core/client/api/jobs/create_job.py +20 -12
  35. blaxel/core/client/api/jobs/create_job_execution.py +22 -16
  36. blaxel/core/client/api/jobs/delete_job.py +12 -8
  37. blaxel/core/client/api/jobs/delete_job_execution.py +12 -8
  38. blaxel/core/client/api/jobs/get_job.py +24 -20
  39. blaxel/core/client/api/jobs/get_job_execution.py +8 -4
  40. blaxel/core/client/api/jobs/list_job_executions.py +8 -4
  41. blaxel/core/client/api/jobs/list_jobs.py +12 -8
  42. blaxel/core/client/api/jobs/update_job.py +20 -12
  43. blaxel/core/client/api/locations/list_locations.py +12 -8
  44. blaxel/core/client/api/{default → mcphub}/list_mcp_hub_definitions.py +20 -4
  45. blaxel/core/client/api/models/create_model.py +52 -23
  46. blaxel/core/client/api/models/delete_model.py +40 -19
  47. blaxel/core/client/api/models/get_model.py +40 -19
  48. blaxel/core/client/api/models/list_models.py +40 -19
  49. blaxel/core/client/api/models/update_model.py +52 -23
  50. blaxel/core/client/api/policies/create_policy.py +12 -8
  51. blaxel/core/client/api/policies/delete_policy.py +12 -8
  52. blaxel/core/client/api/policies/get_policy.py +12 -8
  53. blaxel/core/client/api/policies/list_policies.py +12 -8
  54. blaxel/core/client/api/policies/update_policy.py +12 -8
  55. blaxel/core/client/api/public_ipslist/list_public_ips.py +37 -5
  56. blaxel/core/client/api/sandboxhub/__init__.py +0 -0
  57. blaxel/core/client/api/{default → sandboxhub}/list_sandbox_hub_definitions.py +20 -4
  58. blaxel/core/client/api/service_accounts/create_api_key_for_service_account.py +12 -8
  59. blaxel/core/client/api/service_accounts/create_workspace_service_account.py +12 -8
  60. blaxel/core/client/api/service_accounts/delete_api_key_for_service_account.py +6 -4
  61. blaxel/core/client/api/service_accounts/delete_workspace_service_account.py +12 -8
  62. blaxel/core/client/api/service_accounts/get_workspace_service_accounts.py +12 -8
  63. blaxel/core/client/api/service_accounts/list_api_keys_for_service_account.py +12 -8
  64. blaxel/core/client/api/service_accounts/update_workspace_service_account.py +8 -8
  65. blaxel/core/client/api/templates/list_templates.py +12 -8
  66. blaxel/core/client/api/volume_templates/create_volume_template.py +8 -4
  67. blaxel/core/client/api/volume_templates/list_volume_templates.py +8 -4
  68. blaxel/core/client/api/volumes/create_volume.py +56 -23
  69. blaxel/core/client/api/volumes/delete_volume.py +44 -19
  70. blaxel/core/client/api/volumes/get_volume.py +40 -19
  71. blaxel/core/client/api/volumes/list_volumes.py +40 -19
  72. blaxel/core/client/api/workspaces/create_workspace.py +54 -23
  73. blaxel/core/client/api/workspaces/delete_workspace.py +42 -19
  74. blaxel/core/client/api/workspaces/get_workspace.py +42 -19
  75. blaxel/core/client/api/workspaces/invite_workspace_user.py +8 -4
  76. blaxel/core/client/api/workspaces/list_workspace_users.py +12 -8
  77. blaxel/core/client/api/workspaces/list_workspaces.py +36 -19
  78. blaxel/core/client/api/workspaces/update_workspace.py +50 -19
  79. blaxel/core/client/models/__init__.py +76 -146
  80. blaxel/core/client/models/agent.py +43 -47
  81. blaxel/core/client/models/agent_runtime.py +139 -0
  82. blaxel/core/client/models/agent_runtime_generation.py +18 -0
  83. blaxel/core/client/models/agent_spec.py +33 -110
  84. blaxel/core/client/models/api_key.py +5 -4
  85. blaxel/core/client/models/core_event.py +5 -5
  86. blaxel/core/client/models/create_api_key_for_service_account_body.py +2 -1
  87. blaxel/core/client/models/create_job_execution_request.py +1 -1
  88. blaxel/core/client/models/create_job_execution_response.py +13 -9
  89. blaxel/core/client/models/custom_domain.py +19 -36
  90. blaxel/core/client/models/custom_domain_metadata.py +4 -3
  91. blaxel/core/client/models/custom_domain_spec.py +14 -5
  92. blaxel/core/client/models/custom_domain_spec_status.py +19 -0
  93. blaxel/core/client/models/entrypoint.py +39 -13
  94. blaxel/core/client/models/{workspace_labels.py → entrypoint_args_item.py} +6 -6
  95. blaxel/core/client/models/entrypoint_env.py +3 -3
  96. blaxel/core/client/models/{job_metrics_executions_total.py → entrypoint_super_gateway_args_item.py} +6 -6
  97. blaxel/core/client/models/{spec_configuration.py → env.py} +17 -8
  98. blaxel/core/{sandbox/client/models/welcome_response.py → client/models/error.py} +26 -23
  99. blaxel/core/client/models/expiration_policy.py +30 -11
  100. blaxel/core/client/models/expiration_policy_action.py +17 -0
  101. blaxel/core/client/models/expiration_policy_type.py +19 -0
  102. blaxel/core/client/models/flavor.py +13 -5
  103. blaxel/core/client/models/flavor_type.py +18 -0
  104. blaxel/core/client/models/form.py +6 -6
  105. blaxel/core/client/models/function.py +43 -47
  106. blaxel/core/client/models/function_runtime.py +138 -0
  107. blaxel/core/client/models/function_runtime_generation.py +18 -0
  108. blaxel/core/client/models/function_spec.py +27 -73
  109. blaxel/core/client/models/function_spec_transport.py +18 -0
  110. blaxel/core/client/models/image.py +19 -36
  111. blaxel/core/client/models/integration_connection.py +25 -39
  112. blaxel/core/client/models/integration_connection_spec.py +8 -5
  113. blaxel/core/client/models/integration_connection_spec_config.py +1 -1
  114. blaxel/core/client/models/integration_connection_spec_secret.py +1 -1
  115. blaxel/core/client/models/integration_endpoint.py +41 -11
  116. blaxel/core/client/models/integration_endpoint_ignore_models_item.py +45 -0
  117. blaxel/core/client/models/{mcp_definition_entrypoint.py → integration_endpoint_models_item.py} +6 -6
  118. blaxel/core/client/models/job.py +43 -47
  119. blaxel/core/client/models/job_execution.py +30 -37
  120. blaxel/core/client/models/job_execution_metadata.py +3 -3
  121. blaxel/core/client/models/job_execution_spec.py +2 -2
  122. blaxel/core/client/models/job_execution_stats.py +5 -5
  123. blaxel/core/client/models/job_execution_status.py +24 -0
  124. blaxel/core/client/models/job_execution_task.py +12 -4
  125. blaxel/core/client/models/job_execution_task_metadata.py +1 -1
  126. blaxel/core/client/models/job_execution_task_spec.py +2 -2
  127. blaxel/core/client/models/job_execution_task_status.py +23 -0
  128. blaxel/core/client/models/job_runtime.py +172 -0
  129. blaxel/core/client/models/job_runtime_generation.py +18 -0
  130. blaxel/core/client/models/job_spec.py +20 -88
  131. blaxel/core/client/models/location_response.py +5 -5
  132. blaxel/core/client/models/mcp_definition.py +30 -17
  133. blaxel/core/client/models/{job_metrics_tasks_total.py → mcp_definition_categories_item.py} +6 -6
  134. blaxel/core/client/models/metadata.py +23 -17
  135. blaxel/core/client/models/metadata_labels.py +4 -1
  136. blaxel/core/client/models/model.py +43 -47
  137. blaxel/core/client/models/model_runtime.py +99 -0
  138. blaxel/core/client/models/model_runtime_type.py +34 -0
  139. blaxel/core/client/models/model_spec.py +12 -58
  140. blaxel/core/client/models/o_auth.py +23 -6
  141. blaxel/core/client/models/{form_oauth.py → o_auth_scope_item.py} +6 -6
  142. blaxel/core/client/models/pending_invitation_accept.py +2 -1
  143. blaxel/core/client/models/pending_invitation_workspace_details.py +27 -6
  144. blaxel/core/client/models/{metrics_request_total_per_code.py → pending_invitation_workspace_details_emails_item.py} +6 -6
  145. blaxel/core/client/models/policy.py +20 -36
  146. blaxel/core/client/models/policy_location.py +13 -5
  147. blaxel/core/client/models/policy_location_type.py +19 -0
  148. blaxel/core/client/models/policy_max_tokens.py +6 -6
  149. blaxel/core/client/models/policy_resource_type.py +20 -0
  150. blaxel/core/client/models/policy_spec.py +31 -10
  151. blaxel/core/client/models/policy_spec_type.py +19 -0
  152. blaxel/core/client/models/port.py +25 -15
  153. blaxel/core/client/models/port_protocol.py +19 -0
  154. blaxel/core/client/models/preview.py +19 -36
  155. blaxel/core/client/models/preview_metadata.py +12 -10
  156. blaxel/core/client/models/preview_token.py +19 -36
  157. blaxel/core/client/models/preview_token_metadata.py +8 -6
  158. blaxel/core/client/models/repository.py +2 -2
  159. blaxel/core/client/models/revision_configuration.py +3 -3
  160. blaxel/core/client/models/sandbox.py +45 -58
  161. blaxel/core/client/models/sandbox_definition.py +37 -22
  162. blaxel/core/client/models/sandbox_definition_categories_item.py +45 -0
  163. blaxel/core/client/models/sandbox_error.py +148 -0
  164. blaxel/core/client/models/sandbox_error_details.py +45 -0
  165. blaxel/core/client/models/sandbox_lifecycle.py +3 -2
  166. blaxel/core/client/models/sandbox_runtime.py +145 -0
  167. blaxel/core/client/models/sandbox_spec.py +33 -134
  168. blaxel/core/client/models/status.py +25 -0
  169. blaxel/core/client/models/template.py +8 -7
  170. blaxel/core/client/models/template_variable.py +5 -5
  171. blaxel/core/client/models/trigger.py +14 -6
  172. blaxel/core/client/models/trigger_configuration.py +7 -6
  173. blaxel/core/client/models/trigger_type.py +19 -0
  174. blaxel/core/client/models/volume.py +35 -47
  175. blaxel/core/client/models/volume_attachment.py +6 -4
  176. blaxel/core/client/models/volume_spec.py +7 -4
  177. blaxel/core/client/models/volume_state.py +3 -3
  178. blaxel/core/client/models/volume_template.py +19 -33
  179. blaxel/core/client/models/volume_template_state.py +12 -4
  180. blaxel/core/client/models/volume_template_state_status.py +19 -0
  181. blaxel/core/client/models/volume_template_version.py +12 -4
  182. blaxel/core/client/models/volume_template_version_status.py +19 -0
  183. blaxel/core/client/models/workspace.py +35 -25
  184. blaxel/core/client/models/workspace_runtime.py +3 -2
  185. blaxel/core/client/models/workspace_status.py +22 -0
  186. blaxel/core/common/__init__.py +1 -1
  187. blaxel/core/jobs/__init__.py +0 -1
  188. blaxel/core/sandbox/__init__.py +2 -0
  189. blaxel/core/sandbox/client/api/process/post_process.py +8 -4
  190. blaxel/core/sandbox/client/models/__init__.py +0 -2
  191. blaxel/core/sandbox/client/models/process_response.py +16 -0
  192. blaxel/core/sandbox/client/models/process_response_status.py +9 -0
  193. blaxel/core/sandbox/default/__init__.py +2 -0
  194. blaxel/core/sandbox/default/interpreter.py +5 -1
  195. blaxel/core/sandbox/default/preview.py +3 -1
  196. blaxel/core/sandbox/default/sandbox.py +85 -26
  197. blaxel/core/sandbox/sync/process.py +2 -1
  198. blaxel/core/sandbox/sync/sandbox.py +68 -23
  199. blaxel/core/sandbox/types.py +3 -0
  200. blaxel/core/tools/common.py +16 -2
  201. blaxel/core/volume/__init__.py +2 -2
  202. blaxel/core/volume/volume.py +227 -11
  203. blaxel/langgraph/tools.py +34 -2
  204. blaxel/openai/tools.py +33 -1
  205. {blaxel-0.2.33.dist-info → blaxel-0.2.35.dist-info}/METADATA +3 -3
  206. {blaxel-0.2.33.dist-info → blaxel-0.2.35.dist-info}/RECORD +209 -248
  207. blaxel/core/client/models/acl.py +0 -133
  208. blaxel/core/client/models/billable_time_metric.py +0 -89
  209. blaxel/core/client/models/core_spec.py +0 -194
  210. blaxel/core/client/models/core_spec_configurations.py +0 -77
  211. blaxel/core/client/models/histogram_bucket.py +0 -79
  212. blaxel/core/client/models/histogram_stats.py +0 -88
  213. blaxel/core/client/models/integration_model.py +0 -162
  214. blaxel/core/client/models/job_execution_config.py +0 -79
  215. blaxel/core/client/models/job_metrics.py +0 -262
  216. blaxel/core/client/models/jobs_chart_value.py +0 -70
  217. blaxel/core/client/models/jobs_network_chart.py +0 -102
  218. blaxel/core/client/models/jobs_success_failed_chart.py +0 -147
  219. blaxel/core/client/models/jobs_total.py +0 -88
  220. blaxel/core/client/models/last_n_requests_metric.py +0 -97
  221. blaxel/core/client/models/latency_metric.py +0 -148
  222. blaxel/core/client/models/logs_response.py +0 -63
  223. blaxel/core/client/models/logs_response_data.py +0 -99
  224. blaxel/core/client/models/mcp_definition_form.py +0 -45
  225. blaxel/core/client/models/memory_allocation_by_name.py +0 -70
  226. blaxel/core/client/models/memory_allocation_metric.py +0 -61
  227. blaxel/core/client/models/metric.py +0 -79
  228. blaxel/core/client/models/metrics.py +0 -273
  229. blaxel/core/client/models/metrics_models.py +0 -45
  230. blaxel/core/client/models/metrics_rps_per_code.py +0 -45
  231. blaxel/core/client/models/pod_template_spec.py +0 -45
  232. blaxel/core/client/models/request_duration_over_time_metric.py +0 -97
  233. blaxel/core/client/models/request_duration_over_time_metrics.py +0 -84
  234. blaxel/core/client/models/request_total_by_origin_metric.py +0 -129
  235. blaxel/core/client/models/request_total_by_origin_metric_request_total_by_origin.py +0 -45
  236. blaxel/core/client/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +0 -45
  237. blaxel/core/client/models/request_total_metric.py +0 -155
  238. blaxel/core/client/models/request_total_metric_request_total_per_code.py +0 -45
  239. blaxel/core/client/models/request_total_metric_rps_per_code.py +0 -45
  240. blaxel/core/client/models/request_total_response_data.py +0 -97
  241. blaxel/core/client/models/resource.py +0 -99
  242. blaxel/core/client/models/resource_log.py +0 -88
  243. blaxel/core/client/models/resource_log_chart.py +0 -133
  244. blaxel/core/client/models/resource_log_response.py +0 -83
  245. blaxel/core/client/models/resource_metrics.py +0 -618
  246. blaxel/core/client/models/resource_metrics_request_total_per_code.py +0 -45
  247. blaxel/core/client/models/resource_metrics_request_total_per_code_previous.py +0 -45
  248. blaxel/core/client/models/resource_metrics_rps_per_code.py +0 -45
  249. blaxel/core/client/models/resource_metrics_rps_per_code_previous.py +0 -45
  250. blaxel/core/client/models/resource_trace.py +0 -97
  251. blaxel/core/client/models/runtime.py +0 -317
  252. blaxel/core/client/models/runtime_configuration.py +0 -45
  253. blaxel/core/client/models/runtime_startup_probe.py +0 -45
  254. blaxel/core/client/models/sandbox_metrics.py +0 -88
  255. blaxel/core/client/models/serverless_config.py +0 -117
  256. blaxel/core/client/models/serverless_config_configuration.py +0 -45
  257. blaxel/core/client/models/start_sandbox.py +0 -98
  258. blaxel/core/client/models/stop_sandbox.py +0 -98
  259. blaxel/core/client/models/store_agent.py +0 -181
  260. blaxel/core/client/models/store_agent_labels.py +0 -45
  261. blaxel/core/client/models/store_configuration.py +0 -156
  262. blaxel/core/client/models/store_configuration_option.py +0 -79
  263. blaxel/core/client/models/time_to_first_token_over_time_metrics.py +0 -87
  264. blaxel/core/client/models/token_rate_metric.py +0 -106
  265. blaxel/core/client/models/token_rate_metrics.py +0 -124
  266. blaxel/core/client/models/token_total_metric.py +0 -112
  267. blaxel/core/client/models/trace_ids_response.py +0 -45
  268. blaxel/core/client/models/websocket_channel.py +0 -97
  269. blaxel/core/client/models/websocket_message.py +0 -106
  270. blaxel/core/sandbox/client/api/root/delete.py +0 -130
  271. blaxel/core/sandbox/client/api/root/get.py +0 -130
  272. blaxel/core/sandbox/client/api/root/options.py +0 -130
  273. blaxel/core/sandbox/client/api/root/patch.py +0 -130
  274. blaxel/core/sandbox/client/api/root/post.py +0 -130
  275. blaxel/core/sandbox/client/api/root/put.py +0 -130
  276. /blaxel/core/{sandbox/client/api/root → client/api/mcphub}/__init__.py +0 -0
  277. {blaxel-0.2.33.dist-info → blaxel-0.2.35.dist-info}/WHEEL +0 -0
  278. {blaxel-0.2.33.dist-info → blaxel-0.2.35.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,22 @@
1
+ from enum import Enum
2
+
3
+
4
+ class WorkspaceStatus(str, Enum):
5
+ ACCOUNT_BINDED = "account_binded"
6
+ ACCOUNT_CONFIGURED = "account_configured"
7
+ CREATED = "created"
8
+ ERROR = "error"
9
+ READY = "ready"
10
+ WORKSPACE_CONFIGURED = "workspace_configured"
11
+
12
+ def __str__(self) -> str:
13
+ return str(self.value)
14
+
15
+ @classmethod
16
+ def _missing_(cls, value: object) -> "WorkspaceStatus | None":
17
+ if isinstance(value, str):
18
+ upper_value = value.upper()
19
+ for member in cls:
20
+ if member.value.upper() == upper_value:
21
+ return member
22
+ return None
@@ -1,7 +1,7 @@
1
1
  from .autoload import autoload
2
2
  from .env import env
3
- from .sentry import capture_exception, flush_sentry, init_sentry, is_sentry_initialized
4
3
  from .internal import get_alphanumeric_limited_hash, get_global_unique_hash
4
+ from .sentry import capture_exception, flush_sentry, init_sentry, is_sentry_initialized
5
5
  from .settings import Settings, settings
6
6
  from .webhook import (
7
7
  AsyncSidecarCallback,
@@ -229,7 +229,6 @@ class BlJob:
229
229
  client=client,
230
230
  body=request,
231
231
  )
232
-
233
232
  if response.status_code != 200:
234
233
  raise Exception(f"Failed to create job execution: {response.status_code}")
235
234
 
@@ -6,6 +6,7 @@ from .client.models import (
6
6
  )
7
7
  from .default import (
8
8
  CodeInterpreter,
9
+ SandboxAPIError,
9
10
  SandboxCodegen,
10
11
  SandboxFileSystem,
11
12
  SandboxInstance,
@@ -34,6 +35,7 @@ from .types import (
34
35
 
35
36
  __all__ = [
36
37
  "SandboxInstance",
38
+ "SandboxAPIError",
37
39
  "SessionCreateOptions",
38
40
  "SessionWithToken",
39
41
  "SandboxConfiguration",
@@ -77,7 +77,8 @@ def sync_detailed(
77
77
  ) -> Response[Union[ErrorResponse, ProcessResponse]]:
78
78
  """Execute a command
79
79
 
80
- Execute a command and return process information
80
+ Execute a command and return process information. If Accept header is text/event-stream, streams
81
+ logs in SSE format and returns the process response as a final event.
81
82
 
82
83
  Args:
83
84
  body (ProcessRequest):
@@ -108,7 +109,8 @@ def sync(
108
109
  ) -> Union[ErrorResponse, ProcessResponse] | None:
109
110
  """Execute a command
110
111
 
111
- Execute a command and return process information
112
+ Execute a command and return process information. If Accept header is text/event-stream, streams
113
+ logs in SSE format and returns the process response as a final event.
112
114
 
113
115
  Args:
114
116
  body (ProcessRequest):
@@ -134,7 +136,8 @@ async def asyncio_detailed(
134
136
  ) -> Response[Union[ErrorResponse, ProcessResponse]]:
135
137
  """Execute a command
136
138
 
137
- Execute a command and return process information
139
+ Execute a command and return process information. If Accept header is text/event-stream, streams
140
+ logs in SSE format and returns the process response as a final event.
138
141
 
139
142
  Args:
140
143
  body (ProcessRequest):
@@ -163,7 +166,8 @@ async def asyncio(
163
166
  ) -> Union[ErrorResponse, ProcessResponse] | None:
164
167
  """Execute a command
165
168
 
166
- Execute a command and return process information
169
+ Execute a command and return process information. If Accept header is text/event-stream, streams
170
+ logs in SSE format and returns the process response as a final event.
167
171
 
168
172
  Args:
169
173
  body (ProcessRequest):
@@ -41,7 +41,6 @@ from .subdirectory import Subdirectory
41
41
  from .success_response import SuccessResponse
42
42
  from .tree_request import TreeRequest
43
43
  from .tree_request_files import TreeRequestFiles
44
- from .welcome_response import WelcomeResponse
45
44
 
46
45
  __all__ = (
47
46
  "ApplyEditRequest",
@@ -83,5 +82,4 @@ __all__ = (
83
82
  "SuccessResponse",
84
83
  "TreeRequest",
85
84
  "TreeRequestFiles",
86
- "WelcomeResponse",
87
85
  )
@@ -21,6 +21,8 @@ class ProcessResponse:
21
21
  pid (str): Example: 1234.
22
22
  started_at (str): Example: Wed, 01 Jan 2023 12:00:00 GMT.
23
23
  status (ProcessResponseStatus): Example: running.
24
+ stderr (str): Example: stderr output.
25
+ stdout (str): Example: stdout output.
24
26
  working_dir (str): Example: /home/user.
25
27
  max_restarts (Union[Unset, int]): Example: 3.
26
28
  restart_count (Union[Unset, int]): Example: 2.
@@ -35,6 +37,8 @@ class ProcessResponse:
35
37
  pid: str
36
38
  started_at: str
37
39
  status: ProcessResponseStatus
40
+ stderr: str
41
+ stdout: str
38
42
  working_dir: str
39
43
  max_restarts: Union[Unset, int] = UNSET
40
44
  restart_count: Union[Unset, int] = UNSET
@@ -58,6 +62,10 @@ class ProcessResponse:
58
62
 
59
63
  status = self.status.value
60
64
 
65
+ stderr = self.stderr
66
+
67
+ stdout = self.stdout
68
+
61
69
  working_dir = self.working_dir
62
70
 
63
71
  max_restarts = self.max_restarts
@@ -78,6 +86,8 @@ class ProcessResponse:
78
86
  "pid": pid,
79
87
  "startedAt": started_at,
80
88
  "status": status,
89
+ "stderr": stderr,
90
+ "stdout": stdout,
81
91
  "workingDir": working_dir,
82
92
  }
83
93
  )
@@ -111,6 +121,10 @@ class ProcessResponse:
111
121
 
112
122
  status = ProcessResponseStatus(d.pop("status"))
113
123
 
124
+ stderr = d.pop("stderr")
125
+
126
+ stdout = d.pop("stdout")
127
+
114
128
  working_dir = d.pop("workingDir") if "workingDir" in d else d.pop("working_dir")
115
129
 
116
130
  max_restarts = d.pop("maxRestarts", d.pop("max_restarts", UNSET))
@@ -128,6 +142,8 @@ class ProcessResponse:
128
142
  pid=pid,
129
143
  started_at=started_at,
130
144
  status=status,
145
+ stderr=stderr,
146
+ stdout=stdout,
131
147
  working_dir=working_dir,
132
148
  max_restarts=max_restarts,
133
149
  restart_count=restart_count,
@@ -10,3 +10,12 @@ class ProcessResponseStatus(str, Enum):
10
10
 
11
11
  def __str__(self) -> str:
12
12
  return str(self.value)
13
+
14
+ @classmethod
15
+ def _missing_(cls, value: object) -> "ProcessResponseStatus | None":
16
+ if isinstance(value, str):
17
+ upper_value = value.upper()
18
+ for member in cls:
19
+ if member.value.upper() == upper_value:
20
+ return member
21
+ return None
@@ -1,5 +1,6 @@
1
1
  from .interpreter import CodeInterpreter
2
2
  from .sandbox import (
3
+ SandboxAPIError,
3
4
  SandboxCodegen,
4
5
  SandboxFileSystem,
5
6
  SandboxInstance,
@@ -9,6 +10,7 @@ from .sandbox import (
9
10
 
10
11
  __all__ = [
11
12
  "SandboxInstance",
13
+ "SandboxAPIError",
12
14
  "SandboxFileSystem",
13
15
  "SandboxPreviews",
14
16
  "SandboxProcess",
@@ -52,7 +52,7 @@ class CodeInterpreter(SandboxInstance):
52
52
  }
53
53
 
54
54
  # Whitelist a minimal set of fields that can be propagated from input
55
- allowed_copy_keys = {"name", "envs", "memory", "region", "headers"}
55
+ allowed_copy_keys = {"name", "envs", "memory", "region", "headers", "labels"}
56
56
 
57
57
  if isinstance(sandbox, dict):
58
58
  for k in allowed_copy_keys:
@@ -67,10 +67,14 @@ class CodeInterpreter(SandboxInstance):
67
67
  payload["memory"] = sandbox.memory
68
68
  if getattr(sandbox, "region", None):
69
69
  payload["region"] = sandbox.region
70
+ if getattr(sandbox, "labels", None):
71
+ payload["labels"] = sandbox.labels
70
72
  elif isinstance(sandbox, Sandbox):
71
73
  # Extract a few basics if available
72
74
  if sandbox.metadata and getattr(sandbox.metadata, "name", None):
73
75
  payload["name"] = sandbox.metadata.name
76
+ if sandbox.metadata and getattr(sandbox.metadata, "labels", None):
77
+ payload["labels"] = sandbox.metadata.labels
74
78
  if sandbox.spec and sandbox.spec.runtime:
75
79
  if getattr(sandbox.spec.runtime, "envs", None):
76
80
  payload["envs"] = sandbox.spec.runtime.envs
@@ -29,6 +29,7 @@ from ...client.models import (
29
29
  Preview,
30
30
  PreviewSpec,
31
31
  PreviewToken,
32
+ PreviewTokenMetadata,
32
33
  PreviewTokenSpec,
33
34
  Sandbox,
34
35
  )
@@ -69,9 +70,10 @@ class SandboxPreviewTokens:
69
70
  self.resource_name,
70
71
  self.preview_name,
71
72
  body=PreviewToken(
73
+ metadata=PreviewTokenMetadata(name=""),
72
74
  spec=PreviewTokenSpec(
73
75
  expires_at=to_utc_z(expires_at),
74
- )
76
+ ),
75
77
  ),
76
78
  client=client,
77
79
  )
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import uuid
3
- from typing import Any, Dict, List, Union
3
+ from typing import Any, Callable, Dict, List, Union
4
4
 
5
5
  from ...client.api.compute.create_sandbox import asyncio as create_sandbox
6
6
  from ...client.api.compute.delete_sandbox import asyncio as delete_sandbox
@@ -8,7 +8,9 @@ from ...client.api.compute.get_sandbox import asyncio as get_sandbox
8
8
  from ...client.api.compute.list_sandboxes import asyncio as list_sandboxes
9
9
  from ...client.api.compute.update_sandbox import asyncio as update_sandbox
10
10
  from ...client.client import client
11
- from ...client.models import Metadata, Runtime, Sandbox, SandboxSpec
11
+ from ...client.models import Metadata, Sandbox, SandboxRuntime, SandboxSpec
12
+ from ...client.models.error import Error
13
+ from ...client.models.sandbox_error import SandboxError
12
14
  from ...client.types import UNSET
13
15
  from ..types import (
14
16
  SandboxConfiguration,
@@ -23,10 +25,40 @@ from .preview import SandboxPreviews
23
25
  from .process import SandboxProcess
24
26
  from .session import SandboxSessions
25
27
 
28
+
29
+ class SandboxAPIError(Exception):
30
+ """Exception raised when sandbox API returns an error."""
31
+
32
+ def __init__(self, message: str, status_code: int | None = None, code: str | None = None):
33
+ super().__init__(message)
34
+ self.status_code = status_code
35
+ self.code = code
36
+
37
+
26
38
  logger = logging.getLogger(__name__)
27
39
 
28
40
 
41
+ class _AsyncDeleteDescriptor:
42
+ """Descriptor that provides both class-level and instance-level delete functionality."""
43
+
44
+ def __init__(self, delete_func: Callable):
45
+ self._delete_func = delete_func
46
+
47
+ def __get__(self, instance, owner):
48
+ if instance is None:
49
+ # Called on the class: SandboxInstance.delete("name")
50
+ return self._delete_func
51
+ else:
52
+ # Called on an instance: instance.delete()
53
+ async def instance_delete() -> Sandbox:
54
+ return await self._delete_func(instance.metadata.name)
55
+
56
+ return instance_delete
57
+
58
+
29
59
  class SandboxInstance:
60
+ delete: "_AsyncDeleteDescriptor"
61
+
30
62
  def __init__(
31
63
  self,
32
64
  sandbox: Union[Sandbox, SandboxConfiguration],
@@ -107,6 +139,7 @@ class SandboxInstance:
107
139
  or "lifecycle" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
108
140
  or "snapshot_enabled"
109
141
  in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
142
+ or "labels" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
110
143
  )
111
144
  )
112
145
  ):
@@ -135,14 +168,13 @@ class SandboxInstance:
135
168
 
136
169
  # Create full Sandbox object
137
170
  sandbox = Sandbox(
138
- metadata=Metadata(name=name),
171
+ metadata=Metadata(name=name, labels=config.labels),
139
172
  spec=SandboxSpec(
140
- runtime=Runtime(
173
+ runtime=SandboxRuntime(
141
174
  image=image,
142
175
  memory=memory,
143
176
  ports=ports,
144
177
  envs=envs,
145
- generation="mk3",
146
178
  ),
147
179
  volumes=volumes,
148
180
  ),
@@ -167,19 +199,26 @@ class SandboxInstance:
167
199
  sandbox.metadata = Metadata(name=default_name)
168
200
  if not sandbox.spec:
169
201
  sandbox.spec = SandboxSpec(
170
- runtime=Runtime(image=default_image, memory=default_memory)
202
+ runtime=SandboxRuntime(image=default_image, memory=default_memory)
171
203
  )
172
204
  if not sandbox.spec.runtime:
173
- sandbox.spec.runtime = Runtime(image=default_image, memory=default_memory)
205
+ sandbox.spec.runtime = SandboxRuntime(image=default_image, memory=default_memory)
174
206
 
175
207
  sandbox.spec.runtime.image = sandbox.spec.runtime.image or default_image
176
208
  sandbox.spec.runtime.memory = sandbox.spec.runtime.memory or default_memory
177
- sandbox.spec.runtime.generation = sandbox.spec.runtime.generation or "mk3"
178
209
 
179
210
  response = await create_sandbox(
180
211
  client=client,
181
212
  body=sandbox,
182
213
  )
214
+
215
+ # Check if response is an error
216
+ if isinstance(response, SandboxError):
217
+ status_code = response.status_code if response.status_code is not UNSET else None
218
+ code = response.code if response.code else None
219
+ message = response.message if response.message else str(response)
220
+ raise SandboxAPIError(message, status_code=status_code, code=code)
221
+
183
222
  instance = cls(response)
184
223
  # TODO remove this part once we have a better way to handle this
185
224
  if safe:
@@ -195,21 +234,23 @@ class SandboxInstance:
195
234
  sandbox_name,
196
235
  client=client,
197
236
  )
237
+
238
+ # Check if response is an error
239
+ if isinstance(response, Error):
240
+ status_code = response.code if response.code is not UNSET else None
241
+ message = response.message if response.message is not UNSET else response.error
242
+ raise SandboxAPIError(message, status_code=status_code, code=response.error)
243
+
244
+ if response is None:
245
+ raise SandboxAPIError(f"Sandbox '{sandbox_name}' not found", status_code=404)
246
+
198
247
  return cls(response)
199
248
 
200
249
  @classmethod
201
250
  async def list(cls) -> List["SandboxInstance"]:
202
- response = await list_sandboxes()
251
+ response = await list_sandboxes(client=client)
203
252
  return [cls(sandbox) for sandbox in response]
204
253
 
205
- @classmethod
206
- async def delete(cls, sandbox_name: str) -> Sandbox:
207
- response = await delete_sandbox(
208
- sandbox_name,
209
- client=client,
210
- )
211
- return response
212
-
213
254
  @classmethod
214
255
  async def update_metadata(
215
256
  cls, sandbox_name: str, metadata: SandboxUpdateMetadata
@@ -229,10 +270,12 @@ class SandboxInstance:
229
270
 
230
271
  # Prepare the updated sandbox object
231
272
  updated_sandbox = Sandbox.from_dict(sandbox.to_dict())
273
+ if updated_sandbox is None:
274
+ raise ValueError(f"Sandbox {sandbox_name} not found")
232
275
 
233
276
  # Merge metadata
234
277
  if updated_sandbox.metadata is None:
235
- updated_sandbox.metadata = Metadata()
278
+ updated_sandbox.metadata = Metadata(name=sandbox_name)
236
279
 
237
280
  # Update labels if provided
238
281
  if metadata.labels is not None:
@@ -240,8 +283,11 @@ class SandboxInstance:
240
283
  if updated_sandbox.metadata.labels is None or updated_sandbox.metadata.labels is UNSET:
241
284
  updated_sandbox.metadata.labels = {}
242
285
  else:
243
- # If labels exist, ensure it's a dict
244
- updated_sandbox.metadata.labels = dict(updated_sandbox.metadata.labels)
286
+ # If labels exist, convert to dict (MetadataLabels stores in additional_properties)
287
+ if hasattr(updated_sandbox.metadata.labels, "to_dict"):
288
+ updated_sandbox.metadata.labels = updated_sandbox.metadata.labels.to_dict()
289
+ else:
290
+ updated_sandbox.metadata.labels = dict(updated_sandbox.metadata.labels)
245
291
  updated_sandbox.metadata.labels.update(metadata.labels)
246
292
 
247
293
  # Update display_name if provided
@@ -265,11 +311,9 @@ class SandboxInstance:
265
311
  """Create a sandbox if it doesn't exist, otherwise return existing."""
266
312
  try:
267
313
  return await cls.create(sandbox)
268
- except Exception as e:
314
+ except SandboxAPIError as e:
269
315
  # Check if it's a 409 conflict error (sandbox already exists)
270
- if (hasattr(e, "status_code") and e.status_code == 409) or (
271
- hasattr(e, "code") and e.code in [409, "SANDBOX_ALREADY_EXISTS"]
272
- ):
316
+ if e.status_code == 409 or e.code in [409, "SANDBOX_ALREADY_EXISTS"]:
273
317
  # Extract name from different configuration types
274
318
  if isinstance(sandbox, SandboxCreateConfiguration):
275
319
  name = sandbox.name
@@ -298,7 +342,7 @@ class SandboxInstance:
298
342
 
299
343
  # Otherwise return the existing active sandbox
300
344
  return sandbox_instance
301
- raise e
345
+ raise
302
346
 
303
347
  @classmethod
304
348
  async def from_session(
@@ -310,7 +354,7 @@ class SandboxInstance:
310
354
 
311
355
  # Create a minimal sandbox configuration for session-based access
312
356
  sandbox_name = session.name.split("-")[0] if "-" in session.name else session.name
313
- sandbox = Sandbox(metadata=Metadata(name=sandbox_name))
357
+ sandbox = Sandbox(metadata=Metadata(name=sandbox_name), spec=SandboxSpec())
314
358
 
315
359
  # Use the constructor with force_url, headers, and params
316
360
  return cls(
@@ -319,3 +363,18 @@ class SandboxInstance:
319
363
  headers={"X-Blaxel-Preview-Token": session.token},
320
364
  params={"bl_preview_token": session.token},
321
365
  )
366
+
367
+
368
+ async def _delete_sandbox_by_name(sandbox_name: str) -> Sandbox:
369
+ """Delete a sandbox by name."""
370
+ response = await delete_sandbox(
371
+ sandbox_name,
372
+ client=client,
373
+ )
374
+ if response is None:
375
+ raise ValueError(f"Sandbox {sandbox_name} not found")
376
+ return response
377
+
378
+
379
+ # Assign the delete descriptor to support both class-level and instance-level calls
380
+ SandboxInstance.delete = _AsyncDeleteDescriptor(_delete_sandbox_by_name)
@@ -186,7 +186,8 @@ class SyncSandboxProcess(SyncSandboxAction):
186
186
 
187
187
  if on_log or on_stdout or on_stderr:
188
188
  stream_control = self._stream_logs(
189
- result.pid, {"on_log": on_log, "on_stdout": on_stdout, "on_stderr": on_stderr}
189
+ result.pid,
190
+ {"on_log": on_log, "on_stdout": on_stdout, "on_stderr": on_stderr},
190
191
  )
191
192
  return ProcessResponseWithLog(
192
193
  result,
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import uuid
3
- from typing import Any, Dict, List, Union
3
+ from typing import Any, Callable, Dict, List, Union
4
4
 
5
5
  from ...client.api.compute.create_sandbox import sync as create_sandbox
6
6
  from ...client.api.compute.delete_sandbox import sync as delete_sandbox
@@ -8,8 +8,11 @@ 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 Metadata, Runtime, Sandbox, SandboxSpec
11
+ from ...client.models import Metadata, Sandbox, SandboxRuntime, SandboxSpec
12
+ from ...client.models.error import Error
13
+ from ...client.models.sandbox_error import SandboxError
12
14
  from ...client.types import UNSET
15
+ from ..default.sandbox import SandboxAPIError
13
16
  from ..types import (
14
17
  SandboxConfiguration,
15
18
  SandboxCreateConfiguration,
@@ -26,6 +29,24 @@ from .session import SyncSandboxSessions
26
29
  logger = logging.getLogger(__name__)
27
30
 
28
31
 
32
+ class _SyncDeleteDescriptor:
33
+ """Descriptor that provides both class-level and instance-level delete functionality."""
34
+
35
+ def __init__(self, delete_func: Callable):
36
+ self._delete_func = delete_func
37
+
38
+ def __get__(self, instance, owner):
39
+ if instance is None:
40
+ # Called on the class: SyncSandboxInstance.delete("name")
41
+ return self._delete_func
42
+ else:
43
+ # Called on an instance: instance.delete()
44
+ def instance_delete() -> Sandbox:
45
+ return self._delete_func(instance.metadata.name)
46
+
47
+ return instance_delete
48
+
49
+
29
50
  class SyncSandboxInstance:
30
51
  def __init__(
31
52
  self,
@@ -102,6 +123,7 @@ class SyncSandboxInstance:
102
123
  or "lifecycle" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
103
124
  or "snapshot_enabled"
104
125
  in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
126
+ or "labels" in (sandbox if isinstance(sandbox, dict) else sandbox.__dict__)
105
127
  )
106
128
  )
107
129
  ):
@@ -125,14 +147,13 @@ class SyncSandboxInstance:
125
147
  region = config.region
126
148
  lifecycle = config.lifecycle
127
149
  sandbox = Sandbox(
128
- metadata=Metadata(name=name),
150
+ metadata=Metadata(name=name, labels=config.labels),
129
151
  spec=SandboxSpec(
130
- runtime=Runtime(
152
+ runtime=SandboxRuntime(
131
153
  image=image,
132
154
  memory=memory,
133
155
  ports=ports,
134
156
  envs=envs,
135
- generation="mk3",
136
157
  ),
137
158
  volumes=volumes,
138
159
  ),
@@ -152,17 +173,24 @@ class SyncSandboxInstance:
152
173
  sandbox.metadata = Metadata(name=default_name)
153
174
  if not sandbox.spec:
154
175
  sandbox.spec = SandboxSpec(
155
- runtime=Runtime(image=default_image, memory=default_memory)
176
+ runtime=SandboxRuntime(image=default_image, memory=default_memory)
156
177
  )
157
178
  if not sandbox.spec.runtime:
158
- sandbox.spec.runtime = Runtime(image=default_image, memory=default_memory)
179
+ sandbox.spec.runtime = SandboxRuntime(image=default_image, memory=default_memory)
159
180
  sandbox.spec.runtime.image = sandbox.spec.runtime.image or default_image
160
181
  sandbox.spec.runtime.memory = sandbox.spec.runtime.memory or default_memory
161
- sandbox.spec.runtime.generation = sandbox.spec.runtime.generation or "mk3"
162
182
  response = create_sandbox(
163
183
  client=client,
164
184
  body=sandbox,
165
185
  )
186
+
187
+ # Check if response is an error
188
+ if isinstance(response, SandboxError):
189
+ status_code = response.status_code if response.status_code is not UNSET else None
190
+ code = response.code if response.code else None
191
+ message = response.message if response.message else str(response)
192
+ raise SandboxAPIError(message, status_code=status_code, code=code)
193
+
166
194
  instance = cls(response)
167
195
  if safe:
168
196
  try:
@@ -177,6 +205,16 @@ class SyncSandboxInstance:
177
205
  sandbox_name,
178
206
  client=client,
179
207
  )
208
+
209
+ # Check if response is an error
210
+ if isinstance(response, Error):
211
+ status_code = response.code if response.code is not UNSET else None
212
+ message = response.message if response.message is not UNSET else response.error
213
+ raise SandboxAPIError(message, status_code=status_code, code=response.error)
214
+
215
+ if response is None:
216
+ raise SandboxAPIError(f"Sandbox '{sandbox_name}' not found", status_code=404)
217
+
180
218
  return cls(response)
181
219
 
182
220
  @classmethod
@@ -184,14 +222,6 @@ class SyncSandboxInstance:
184
222
  response = list_sandboxes(client=client)
185
223
  return [cls(sandbox) for sandbox in response]
186
224
 
187
- @classmethod
188
- def delete(cls, sandbox_name: str) -> Sandbox:
189
- response = delete_sandbox(
190
- sandbox_name,
191
- client=client,
192
- )
193
- return response
194
-
195
225
  @classmethod
196
226
  def update_metadata(
197
227
  cls, sandbox_name: str, metadata: SandboxUpdateMetadata
@@ -205,7 +235,11 @@ class SyncSandboxInstance:
205
235
  if updated_sandbox.metadata.labels is None or updated_sandbox.metadata.labels is UNSET:
206
236
  updated_sandbox.metadata.labels = {}
207
237
  else:
208
- updated_sandbox.metadata.labels = dict(updated_sandbox.metadata.labels)
238
+ # MetadataLabels stores in additional_properties, use to_dict()
239
+ if hasattr(updated_sandbox.metadata.labels, "to_dict"):
240
+ updated_sandbox.metadata.labels = updated_sandbox.metadata.labels.to_dict()
241
+ else:
242
+ updated_sandbox.metadata.labels = dict(updated_sandbox.metadata.labels)
209
243
  updated_sandbox.metadata.labels.update(metadata.labels)
210
244
  if metadata.display_name is not None:
211
245
  updated_sandbox.metadata.display_name = metadata.display_name
@@ -222,10 +256,8 @@ class SyncSandboxInstance:
222
256
  ) -> "SyncSandboxInstance":
223
257
  try:
224
258
  return cls.create(sandbox)
225
- except Exception as e:
226
- if (hasattr(e, "status_code") and e.status_code == 409) or (
227
- hasattr(e, "code") and e.code in [409, "SANDBOX_ALREADY_EXISTS"]
228
- ):
259
+ except SandboxAPIError as e:
260
+ if e.status_code == 409 or e.code in [409, "SANDBOX_ALREADY_EXISTS"]:
229
261
  if isinstance(sandbox, SandboxCreateConfiguration):
230
262
  name = sandbox.name
231
263
  elif isinstance(sandbox, dict):
@@ -245,7 +277,7 @@ class SyncSandboxInstance:
245
277
  if sandbox_instance.status == "TERMINATED":
246
278
  return cls.create(sandbox)
247
279
  return sandbox_instance
248
- raise e
280
+ raise
249
281
 
250
282
  @classmethod
251
283
  def from_session(
@@ -254,10 +286,23 @@ class SyncSandboxInstance:
254
286
  if isinstance(session, dict):
255
287
  session = SessionWithToken.from_dict(session)
256
288
  sandbox_name = session.name.split("-")[0] if "-" in session.name else session.name
257
- sandbox = Sandbox(metadata=Metadata(name=sandbox_name))
289
+ sandbox = Sandbox(metadata=Metadata(name=sandbox_name), spec=SandboxSpec())
258
290
  return cls(
259
291
  sandbox=sandbox,
260
292
  force_url=session.url,
261
293
  headers={"X-Blaxel-Preview-Token": session.token},
262
294
  params={"bl_preview_token": session.token},
263
295
  )
296
+
297
+
298
+ def _delete_sandbox_by_name(sandbox_name: str) -> Sandbox:
299
+ """Delete a sandbox by name."""
300
+ response = delete_sandbox(
301
+ sandbox_name,
302
+ client=client,
303
+ )
304
+ return response
305
+
306
+
307
+ # Assign the delete descriptor to support both class-level and instance-level calls
308
+ SyncSandboxInstance.delete = _SyncDeleteDescriptor(_delete_sandbox_by_name)