loopix-sdk 2.30.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. loopix/__init__.py +260 -0
  2. loopix/api/__init__.py +287 -0
  3. loopix/api/client/__init__.py +8 -0
  4. loopix/api/client/api/__init__.py +1 -0
  5. loopix/api/client/api/sandboxes/__init__.py +1 -0
  6. loopix/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
  7. loopix/api/client/api/sandboxes/get_sandboxes.py +176 -0
  8. loopix/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
  9. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
  10. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
  11. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
  12. loopix/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
  13. loopix/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py +254 -0
  14. loopix/api/client/api/sandboxes/post_sandboxes.py +172 -0
  15. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  16. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +187 -0
  17. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
  18. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
  19. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py +195 -0
  20. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
  21. loopix/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py +199 -0
  22. loopix/api/client/api/snapshots/__init__.py +1 -0
  23. loopix/api/client/api/snapshots/get_snapshots.py +202 -0
  24. loopix/api/client/api/tags/__init__.py +1 -0
  25. loopix/api/client/api/tags/delete_templates_tags.py +174 -0
  26. loopix/api/client/api/tags/get_templates_template_id_tags.py +172 -0
  27. loopix/api/client/api/tags/post_templates_tags.py +176 -0
  28. loopix/api/client/api/templates/__init__.py +1 -0
  29. loopix/api/client/api/templates/delete_templates_template_id.py +157 -0
  30. loopix/api/client/api/templates/get_templates.py +172 -0
  31. loopix/api/client/api/templates/get_templates_aliases_alias.py +167 -0
  32. loopix/api/client/api/templates/get_templates_template_id.py +195 -0
  33. loopix/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py +272 -0
  34. loopix/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +232 -0
  35. loopix/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
  36. loopix/api/client/api/templates/patch_templates_template_id.py +183 -0
  37. loopix/api/client/api/templates/patch_v_2_templates_template_id.py +185 -0
  38. loopix/api/client/api/templates/post_templates.py +172 -0
  39. loopix/api/client/api/templates/post_templates_template_id.py +181 -0
  40. loopix/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
  41. loopix/api/client/api/templates/post_v2_templates.py +172 -0
  42. loopix/api/client/api/templates/post_v3_templates.py +176 -0
  43. loopix/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
  44. loopix/api/client/api/volumes/__init__.py +1 -0
  45. loopix/api/client/api/volumes/delete_volumes_volume_id.py +161 -0
  46. loopix/api/client/api/volumes/get_volumes.py +140 -0
  47. loopix/api/client/api/volumes/get_volumes_volume_id.py +163 -0
  48. loopix/api/client/api/volumes/post_volumes.py +172 -0
  49. loopix/api/client/client.py +286 -0
  50. loopix/api/client/errors.py +16 -0
  51. loopix/api/client/models/__init__.py +185 -0
  52. loopix/api/client/models/admin_build_cancel_result.py +67 -0
  53. loopix/api/client/models/admin_sandbox_kill_result.py +67 -0
  54. loopix/api/client/models/assign_template_tags_request.py +67 -0
  55. loopix/api/client/models/assigned_template_tags.py +68 -0
  56. loopix/api/client/models/aws_registry.py +85 -0
  57. loopix/api/client/models/aws_registry_type.py +8 -0
  58. loopix/api/client/models/build_log_entry.py +89 -0
  59. loopix/api/client/models/build_status_reason.py +95 -0
  60. loopix/api/client/models/connect_sandbox.py +59 -0
  61. loopix/api/client/models/created_access_token.py +100 -0
  62. loopix/api/client/models/created_team_api_key.py +166 -0
  63. loopix/api/client/models/delete_template_tags_request.py +67 -0
  64. loopix/api/client/models/disk_metrics.py +91 -0
  65. loopix/api/client/models/error.py +67 -0
  66. loopix/api/client/models/gcp_registry.py +69 -0
  67. loopix/api/client/models/gcp_registry_type.py +8 -0
  68. loopix/api/client/models/general_registry.py +77 -0
  69. loopix/api/client/models/general_registry_type.py +8 -0
  70. loopix/api/client/models/identifier_masking_details.py +83 -0
  71. loopix/api/client/models/listed_sandbox.py +179 -0
  72. loopix/api/client/models/log_level.py +11 -0
  73. loopix/api/client/models/logs_direction.py +9 -0
  74. loopix/api/client/models/logs_source.py +9 -0
  75. loopix/api/client/models/machine_info.py +83 -0
  76. loopix/api/client/models/max_team_metric.py +78 -0
  77. loopix/api/client/models/mcp_type_0.py +44 -0
  78. loopix/api/client/models/new_access_token.py +59 -0
  79. loopix/api/client/models/new_sandbox.py +224 -0
  80. loopix/api/client/models/new_team_api_key.py +59 -0
  81. loopix/api/client/models/new_volume.py +59 -0
  82. loopix/api/client/models/node.py +160 -0
  83. loopix/api/client/models/node_detail.py +160 -0
  84. loopix/api/client/models/node_metrics.py +122 -0
  85. loopix/api/client/models/node_status.py +12 -0
  86. loopix/api/client/models/node_status_change.py +82 -0
  87. loopix/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
  88. loopix/api/client/models/post_sandboxes_sandbox_id_snapshots_body.py +60 -0
  89. loopix/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
  90. loopix/api/client/models/resumed_sandbox.py +68 -0
  91. loopix/api/client/models/sandbox.py +145 -0
  92. loopix/api/client/models/sandbox_auto_resume_config.py +60 -0
  93. loopix/api/client/models/sandbox_detail.py +267 -0
  94. loopix/api/client/models/sandbox_lifecycle.py +70 -0
  95. loopix/api/client/models/sandbox_log.py +70 -0
  96. loopix/api/client/models/sandbox_log_entry.py +93 -0
  97. loopix/api/client/models/sandbox_log_entry_fields.py +44 -0
  98. loopix/api/client/models/sandbox_logs.py +91 -0
  99. loopix/api/client/models/sandbox_logs_v2_response.py +73 -0
  100. loopix/api/client/models/sandbox_metric.py +126 -0
  101. loopix/api/client/models/sandbox_network_config.py +118 -0
  102. loopix/api/client/models/sandbox_network_config_rules.py +72 -0
  103. loopix/api/client/models/sandbox_network_rule.py +74 -0
  104. loopix/api/client/models/sandbox_network_transform.py +79 -0
  105. loopix/api/client/models/sandbox_network_transform_headers.py +47 -0
  106. loopix/api/client/models/sandbox_network_update_config.py +114 -0
  107. loopix/api/client/models/sandbox_network_update_config_rules.py +71 -0
  108. loopix/api/client/models/sandbox_on_timeout.py +9 -0
  109. loopix/api/client/models/sandbox_pause_request.py +62 -0
  110. loopix/api/client/models/sandbox_state.py +9 -0
  111. loopix/api/client/models/sandbox_volume_mount.py +67 -0
  112. loopix/api/client/models/sandboxes_with_metrics.py +59 -0
  113. loopix/api/client/models/snapshot_info.py +70 -0
  114. loopix/api/client/models/team.py +83 -0
  115. loopix/api/client/models/team_api_key.py +158 -0
  116. loopix/api/client/models/team_metric.py +86 -0
  117. loopix/api/client/models/team_user.py +75 -0
  118. loopix/api/client/models/template.py +225 -0
  119. loopix/api/client/models/template_alias_response.py +67 -0
  120. loopix/api/client/models/template_build.py +139 -0
  121. loopix/api/client/models/template_build_file_upload.py +70 -0
  122. loopix/api/client/models/template_build_info.py +126 -0
  123. loopix/api/client/models/template_build_logs_response.py +73 -0
  124. loopix/api/client/models/template_build_request.py +115 -0
  125. loopix/api/client/models/template_build_request_v2.py +88 -0
  126. loopix/api/client/models/template_build_request_v3.py +107 -0
  127. loopix/api/client/models/template_build_start_v2.py +184 -0
  128. loopix/api/client/models/template_build_status.py +11 -0
  129. loopix/api/client/models/template_legacy.py +207 -0
  130. loopix/api/client/models/template_request_response_v3.py +99 -0
  131. loopix/api/client/models/template_step.py +91 -0
  132. loopix/api/client/models/template_tag.py +78 -0
  133. loopix/api/client/models/template_update_request.py +59 -0
  134. loopix/api/client/models/template_update_response.py +59 -0
  135. loopix/api/client/models/template_with_builds.py +156 -0
  136. loopix/api/client/models/update_team_api_key.py +59 -0
  137. loopix/api/client/models/volume.py +67 -0
  138. loopix/api/client/models/volume_and_token.py +75 -0
  139. loopix/api/client/models/volume_token.py +59 -0
  140. loopix/api/client/py.typed +1 -0
  141. loopix/api/client/types.py +54 -0
  142. loopix/api/client_async/__init__.py +74 -0
  143. loopix/api/client_sync/__init__.py +73 -0
  144. loopix/api/metadata.py +14 -0
  145. loopix/connection_config.py +309 -0
  146. loopix/envd/api.py +170 -0
  147. loopix/envd/filesystem/filesystem_connect.py +193 -0
  148. loopix/envd/filesystem/filesystem_pb2.py +80 -0
  149. loopix/envd/filesystem/filesystem_pb2.pyi +272 -0
  150. loopix/envd/process/process_connect.py +174 -0
  151. loopix/envd/process/process_pb2.py +96 -0
  152. loopix/envd/process/process_pb2.pyi +316 -0
  153. loopix/envd/rpc.py +139 -0
  154. loopix/envd/versions.py +11 -0
  155. loopix/exceptions.py +133 -0
  156. loopix/io_utils.py +57 -0
  157. loopix/paginator.py +52 -0
  158. loopix/py.typed +0 -0
  159. loopix/sandbox/_git/__init__.py +85 -0
  160. loopix/sandbox/_git/args.py +363 -0
  161. loopix/sandbox/_git/auth.py +132 -0
  162. loopix/sandbox/_git/config.py +32 -0
  163. loopix/sandbox/_git/parse.py +222 -0
  164. loopix/sandbox/_git/types.py +149 -0
  165. loopix/sandbox/commands/command_handle.py +69 -0
  166. loopix/sandbox/commands/main.py +39 -0
  167. loopix/sandbox/filesystem/filesystem.py +337 -0
  168. loopix/sandbox/filesystem/watch_handle.py +70 -0
  169. loopix/sandbox/main.py +227 -0
  170. loopix/sandbox/mcp.py +1949 -0
  171. loopix/sandbox/network.py +8 -0
  172. loopix/sandbox/sandbox_api.py +624 -0
  173. loopix/sandbox/signature.py +47 -0
  174. loopix/sandbox/utils.py +34 -0
  175. loopix/sandbox_async/commands/command.py +396 -0
  176. loopix/sandbox_async/commands/command_handle.py +298 -0
  177. loopix/sandbox_async/commands/pty.py +257 -0
  178. loopix/sandbox_async/filesystem/filesystem.py +720 -0
  179. loopix/sandbox_async/filesystem/watch_handle.py +97 -0
  180. loopix/sandbox_async/git.py +1100 -0
  181. loopix/sandbox_async/main.py +987 -0
  182. loopix/sandbox_async/paginator.py +140 -0
  183. loopix/sandbox_async/sandbox_api.py +504 -0
  184. loopix/sandbox_async/utils.py +7 -0
  185. loopix/sandbox_domains.py +5 -0
  186. loopix/sandbox_sync/commands/command.py +420 -0
  187. loopix/sandbox_sync/commands/command_handle.py +239 -0
  188. loopix/sandbox_sync/commands/pty.py +279 -0
  189. loopix/sandbox_sync/filesystem/filesystem.py +710 -0
  190. loopix/sandbox_sync/filesystem/watch_handle.py +102 -0
  191. loopix/sandbox_sync/git.py +1077 -0
  192. loopix/sandbox_sync/main.py +975 -0
  193. loopix/sandbox_sync/paginator.py +140 -0
  194. loopix/sandbox_sync/sandbox_api.py +491 -0
  195. loopix/template/consts.py +45 -0
  196. loopix/template/dockerfile_parser.py +286 -0
  197. loopix/template/logger.py +232 -0
  198. loopix/template/main.py +1368 -0
  199. loopix/template/readycmd.py +144 -0
  200. loopix/template/types.py +194 -0
  201. loopix/template/utils.py +426 -0
  202. loopix/template_async/build_api.py +419 -0
  203. loopix/template_async/main.py +528 -0
  204. loopix/template_sync/build_api.py +409 -0
  205. loopix/template_sync/main.py +529 -0
  206. loopix/volume/client/__init__.py +8 -0
  207. loopix/volume/client/api/__init__.py +1 -0
  208. loopix/volume/client/api/volumes/__init__.py +1 -0
  209. loopix/volume/client/api/volumes/delete_volumecontent_volume_id_path.py +174 -0
  210. loopix/volume/client/api/volumes/get_volumecontent_volume_id_dir.py +204 -0
  211. loopix/volume/client/api/volumes/get_volumecontent_volume_id_file.py +179 -0
  212. loopix/volume/client/api/volumes/get_volumecontent_volume_id_path.py +176 -0
  213. loopix/volume/client/api/volumes/patch_volumecontent_volume_id_path.py +203 -0
  214. loopix/volume/client/api/volumes/post_volumecontent_volume_id_dir.py +239 -0
  215. loopix/volume/client/api/volumes/put_volumecontent_volume_id_file.py +259 -0
  216. loopix/volume/client/client.py +286 -0
  217. loopix/volume/client/errors.py +16 -0
  218. loopix/volume/client/models/__init__.py +13 -0
  219. loopix/volume/client/models/error.py +67 -0
  220. loopix/volume/client/models/patch_volumecontent_volume_id_path_body.py +77 -0
  221. loopix/volume/client/models/volume_entry_stat.py +145 -0
  222. loopix/volume/client/models/volume_entry_stat_type.py +11 -0
  223. loopix/volume/client/py.typed +1 -0
  224. loopix/volume/client/types.py +54 -0
  225. loopix/volume/client_async/__init__.py +88 -0
  226. loopix/volume/client_sync/__init__.py +80 -0
  227. loopix/volume/connection_config.py +145 -0
  228. loopix/volume/types.py +62 -0
  229. loopix/volume/utils.py +52 -0
  230. loopix/volume/volume_async.py +639 -0
  231. loopix/volume/volume_sync.py +639 -0
  232. loopix_connect/__init__.py +1 -0
  233. loopix_connect/client.py +534 -0
  234. loopix_connect/py.typed +0 -0
  235. loopix_sdk-2.30.0.dist-info/METADATA +98 -0
  236. loopix_sdk-2.30.0.dist-info/RECORD +238 -0
  237. loopix_sdk-2.30.0.dist-info/WHEEL +4 -0
  238. loopix_sdk-2.30.0.dist-info/licenses/LICENSE +9 -0
@@ -0,0 +1,140 @@
1
+ import urllib.parse
2
+ from typing import Optional, List
3
+
4
+ from typing_extensions import Unpack
5
+
6
+ from loopix.api.client.api.sandboxes import get_v2_sandboxes
7
+ from loopix.api.client.api.snapshots import get_snapshots
8
+ from loopix.api.client.types import UNSET
9
+ from loopix.connection_config import ApiParams, ConnectionConfig
10
+ from loopix.exceptions import SandboxException
11
+ from loopix.sandbox.sandbox_api import (
12
+ SandboxPaginatorBase,
13
+ SandboxInfo,
14
+ SnapshotPaginatorBase,
15
+ SnapshotInfo,
16
+ )
17
+ from loopix.api import handle_api_exception
18
+ from loopix.api.client.models.error import Error
19
+ from loopix.api.client_async import get_api_client
20
+
21
+
22
+ class AsyncSandboxPaginator(SandboxPaginatorBase):
23
+ """
24
+ Paginator for listing sandboxes.
25
+
26
+ Example:
27
+ ```python
28
+ paginator = AsyncSandbox.list()
29
+
30
+ while paginator.has_next:
31
+ sandboxes = await paginator.next_items()
32
+ print(sandboxes)
33
+ ```
34
+ """
35
+
36
+ async def next_items(self, **opts: Unpack[ApiParams]) -> List[SandboxInfo]:
37
+ """
38
+ Returns the next page of sandboxes.
39
+
40
+ Call this method only if `has_next` is `True`, otherwise it will raise an exception.
41
+
42
+ :param opts: Per-call connection options (e.g. `api_key`, `domain`,
43
+ `headers`, `request_timeout`). When provided, this call uses these
44
+ options instead of the ones the paginator was constructed with.
45
+
46
+ :returns: List of sandboxes
47
+ """
48
+ if not self.has_next:
49
+ raise Exception("No more items to fetch")
50
+
51
+ # Convert filters to the format expected by the API
52
+ metadata: Optional[str] = None
53
+ if self.query and self.query.metadata:
54
+ quoted_metadata = {
55
+ urllib.parse.quote(k): urllib.parse.quote(v)
56
+ for k, v in self.query.metadata.items()
57
+ }
58
+ metadata = urllib.parse.urlencode(quoted_metadata)
59
+
60
+ config = ConnectionConfig(**{**self._opts, **opts})
61
+ api_client = get_api_client(config)
62
+ res = await get_v2_sandboxes.asyncio_detailed(
63
+ client=api_client,
64
+ metadata=metadata if metadata else UNSET,
65
+ state=self.query.state if self.query and self.query.state else UNSET,
66
+ limit=self.limit if self.limit else UNSET,
67
+ next_token=self._next_token if self._next_token else UNSET,
68
+ )
69
+
70
+ if res.status_code >= 300:
71
+ raise handle_api_exception(res)
72
+
73
+ self._update_pagination(res.headers)
74
+
75
+ if res.parsed is None:
76
+ return []
77
+
78
+ # Check if res.parsed is Error
79
+ if isinstance(res.parsed, Error):
80
+ raise SandboxException(f"{res.parsed.message}: Request failed")
81
+
82
+ return [SandboxInfo._from_listed_sandbox(sandbox) for sandbox in res.parsed]
83
+
84
+
85
+ class AsyncSnapshotPaginator(SnapshotPaginatorBase):
86
+ """
87
+ Paginator for listing snapshots.
88
+
89
+ Example:
90
+ ```python
91
+ paginator = AsyncSandbox.list_snapshots()
92
+
93
+ while paginator.has_next:
94
+ snapshots = await paginator.next_items()
95
+ print(snapshots)
96
+ ```
97
+ """
98
+
99
+ async def next_items(self, **opts: Unpack[ApiParams]) -> List[SnapshotInfo]:
100
+ """
101
+ Returns the next page of snapshots.
102
+
103
+ Call this method only if `has_next` is `True`, otherwise it will raise an exception.
104
+
105
+ :param opts: Per-call connection options (e.g. `api_key`, `domain`,
106
+ `headers`, `request_timeout`). When provided, this call uses these
107
+ options instead of the ones the paginator was constructed with.
108
+
109
+ :returns: List of snapshots
110
+ """
111
+ if not self.has_next:
112
+ raise Exception("No more items to fetch")
113
+
114
+ config = ConnectionConfig(**{**self._opts, **opts})
115
+ api_client = get_api_client(config)
116
+ res = await get_snapshots.asyncio_detailed(
117
+ client=api_client,
118
+ sandbox_id=self.sandbox_id if self.sandbox_id else UNSET,
119
+ limit=self.limit if self.limit else UNSET,
120
+ next_token=self._next_token if self._next_token else UNSET,
121
+ )
122
+
123
+ if res.status_code >= 300:
124
+ raise handle_api_exception(res)
125
+
126
+ self._update_pagination(res.headers)
127
+
128
+ if res.parsed is None:
129
+ return []
130
+
131
+ if isinstance(res.parsed, Error):
132
+ raise SandboxException(f"{res.parsed.message}: Request failed")
133
+
134
+ return [
135
+ SnapshotInfo(
136
+ snapshot_id=snapshot.snapshot_id,
137
+ names=list(snapshot.names) if snapshot.names else [],
138
+ )
139
+ for snapshot in res.parsed
140
+ ]
@@ -0,0 +1,504 @@
1
+ import datetime
2
+ import logging
3
+ from typing import Any, Dict, List, Optional, cast
4
+
5
+ from packaging.version import Version
6
+ from typing_extensions import Unpack
7
+
8
+ from loopix.api import SandboxCreateResponse, handle_api_exception
9
+ from loopix.api.client.api.sandboxes import (
10
+ delete_sandboxes_sandbox_id,
11
+ get_sandboxes_sandbox_id,
12
+ get_sandboxes_sandbox_id_metrics,
13
+ post_sandboxes,
14
+ post_sandboxes_sandbox_id_connect,
15
+ post_sandboxes_sandbox_id_pause,
16
+ post_sandboxes_sandbox_id_snapshots,
17
+ post_sandboxes_sandbox_id_timeout,
18
+ put_sandboxes_sandbox_id_network,
19
+ )
20
+ from loopix.api.client.api.templates import delete_templates_template_id
21
+ from loopix.api.client.models import (
22
+ ConnectSandbox,
23
+ Error,
24
+ NewSandbox,
25
+ PostSandboxesSandboxIDSnapshotsBody,
26
+ PostSandboxesSandboxIDTimeoutBody,
27
+ SandboxAutoResumeConfig,
28
+ SandboxNetworkConfig,
29
+ SandboxPauseRequest,
30
+ SandboxVolumeMount as SandboxVolumeMountAPI,
31
+ )
32
+ from loopix.api.client.types import UNSET
33
+ from loopix.api.client_async import get_api_client
34
+ from loopix.connection_config import ApiParams, ConnectionConfig
35
+ from loopix.exceptions import (
36
+ InvalidArgumentException,
37
+ SandboxException,
38
+ SandboxNotFoundException,
39
+ TemplateException,
40
+ )
41
+ from loopix.sandbox.main import SandboxBase
42
+ from loopix.sandbox.sandbox_api import (
43
+ build_network_update_body,
44
+ McpServer,
45
+ SandboxInfo,
46
+ SandboxLifecycle,
47
+ SandboxMetrics,
48
+ SandboxNetworkOpts,
49
+ SandboxNetworkUpdate,
50
+ SandboxQuery,
51
+ SnapshotInfo,
52
+ build_network_config,
53
+ )
54
+ from loopix.sandbox_async.paginator import AsyncSandboxPaginator
55
+
56
+
57
+ class SandboxApi(SandboxBase):
58
+ @staticmethod
59
+ def list(
60
+ query: Optional[SandboxQuery] = None,
61
+ limit: Optional[int] = None,
62
+ next_token: Optional[str] = None,
63
+ **opts: Unpack[ApiParams],
64
+ ) -> AsyncSandboxPaginator:
65
+ """
66
+ List sandboxes.
67
+
68
+ By default (no `query.state` set), returns sandboxes in both `running`
69
+ and `paused` states. To filter by state, pass `query=SandboxQuery(state=[...])`.
70
+
71
+ :param query: Filter the list of sandboxes by metadata or state, e.g. `SandboxQuery(metadata={"key": "value"})` or `SandboxQuery(state=[SandboxState.RUNNING])`
72
+ :param limit: Maximum number of sandboxes to return per page
73
+ :param next_token: Token for pagination
74
+
75
+ :return: An `AsyncSandboxPaginator` that yields pages of sandboxes (running and paused by default). Iterate pages via `await paginator.next_items()` while `paginator.has_next` is True.
76
+ """
77
+ return AsyncSandboxPaginator(
78
+ query=query,
79
+ limit=limit,
80
+ next_token=next_token,
81
+ **opts,
82
+ )
83
+
84
+ @classmethod
85
+ async def _cls_get_info(
86
+ cls,
87
+ sandbox_id: str,
88
+ **opts: Unpack[ApiParams],
89
+ ) -> SandboxInfo:
90
+ """
91
+ Get the sandbox info.
92
+ :param sandbox_id: Sandbox ID
93
+
94
+ :return: Sandbox info
95
+ """
96
+ config = ConnectionConfig(**opts)
97
+
98
+ api_client = get_api_client(config)
99
+ res = await get_sandboxes_sandbox_id.asyncio_detailed(
100
+ sandbox_id,
101
+ client=api_client,
102
+ )
103
+
104
+ if res.status_code == 404:
105
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
106
+
107
+ if res.status_code >= 300:
108
+ raise handle_api_exception(res)
109
+
110
+ if res.parsed is None:
111
+ raise Exception("Body of the request is None")
112
+
113
+ if isinstance(res.parsed, Error):
114
+ raise SandboxException(f"{res.parsed.message}: Request failed")
115
+
116
+ return SandboxInfo._from_sandbox_detail(res.parsed)
117
+
118
+ @classmethod
119
+ async def _cls_kill(
120
+ cls,
121
+ sandbox_id: str,
122
+ **opts: Unpack[ApiParams],
123
+ ) -> bool:
124
+ config = ConnectionConfig(**opts)
125
+
126
+ if config.debug:
127
+ # Skip killing the sandbox in debug mode
128
+ return True
129
+
130
+ api_client = get_api_client(config)
131
+ res = await delete_sandboxes_sandbox_id.asyncio_detailed(
132
+ sandbox_id,
133
+ client=api_client,
134
+ )
135
+
136
+ if res.status_code == 404:
137
+ return False
138
+
139
+ if res.status_code >= 300:
140
+ raise handle_api_exception(res)
141
+
142
+ return True
143
+
144
+ @classmethod
145
+ async def _cls_set_timeout(
146
+ cls,
147
+ sandbox_id: str,
148
+ timeout: int,
149
+ **opts: Unpack[ApiParams],
150
+ ) -> None:
151
+ config = ConnectionConfig(**opts)
152
+
153
+ if config.debug:
154
+ # Skip setting the timeout in debug mode
155
+ return
156
+
157
+ api_client = get_api_client(config)
158
+ res = await post_sandboxes_sandbox_id_timeout.asyncio_detailed(
159
+ sandbox_id,
160
+ client=api_client,
161
+ body=PostSandboxesSandboxIDTimeoutBody(timeout=timeout),
162
+ )
163
+
164
+ if res.status_code == 404:
165
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
166
+
167
+ if res.status_code >= 300:
168
+ raise handle_api_exception(res)
169
+
170
+ @classmethod
171
+ async def _cls_update_network(
172
+ cls,
173
+ sandbox_id: str,
174
+ network: SandboxNetworkUpdate,
175
+ **opts: Unpack[ApiParams],
176
+ ) -> None:
177
+ config = ConnectionConfig(**opts)
178
+
179
+ api_client = get_api_client(config)
180
+ res = await put_sandboxes_sandbox_id_network.asyncio_detailed(
181
+ sandbox_id,
182
+ client=api_client,
183
+ body=build_network_update_body(network),
184
+ )
185
+
186
+ if res.status_code == 404:
187
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
188
+
189
+ if res.status_code >= 300:
190
+ raise handle_api_exception(res)
191
+
192
+ @classmethod
193
+ async def _create_sandbox(
194
+ cls,
195
+ template: str,
196
+ timeout: int,
197
+ allow_internet_access: bool,
198
+ metadata: Optional[Dict[str, str]],
199
+ env_vars: Optional[Dict[str, str]],
200
+ secure: bool,
201
+ mcp: Optional[McpServer] = None,
202
+ network: Optional[SandboxNetworkOpts] = None,
203
+ lifecycle: Optional[SandboxLifecycle] = None,
204
+ volume_mounts: Optional[List[SandboxVolumeMountAPI]] = None,
205
+ logger: Optional[logging.Logger] = None,
206
+ **opts: Unpack[ApiParams],
207
+ ) -> SandboxCreateResponse:
208
+ config = ConnectionConfig(logger=logger, **opts)
209
+
210
+ # on_timeout accepts a bare action or {"action", "keep_memory"}; normalize.
211
+ # Only the object form carries keep_memory; anything else (a bare action
212
+ # string, or an unexpected value from an untyped caller) passes through as
213
+ # the action, so a non-"pause" value resolves to kill instead of crashing.
214
+ on_timeout_raw = lifecycle.get("on_timeout", "kill") if lifecycle else "kill"
215
+ if isinstance(on_timeout_raw, dict):
216
+ on_timeout = on_timeout_raw.get("action", "kill")
217
+ keep_memory_provided = "keep_memory" in on_timeout_raw
218
+ keep_memory = on_timeout_raw.get("keep_memory")
219
+ else:
220
+ on_timeout = on_timeout_raw
221
+ keep_memory = None
222
+ keep_memory_provided = False
223
+
224
+ # keep_memory only governs a pause action. The discriminated union type
225
+ # forbids it on action="kill"; re-check at runtime for callers that
226
+ # bypass the type.
227
+ if keep_memory_provided and on_timeout != "pause":
228
+ raise InvalidArgumentException(
229
+ "keep_memory is only allowed when on_timeout action is 'pause'."
230
+ )
231
+
232
+ # A missing or explicit None keep_memory defaults to True (full memory),
233
+ # mirroring the JS SDK; sending null would wrongly read as filesystem-only.
234
+ if keep_memory is None:
235
+ keep_memory = True
236
+ auto_resume = lifecycle.get("auto_resume", False) if lifecycle else False
237
+
238
+ if auto_resume and on_timeout != "pause":
239
+ raise InvalidArgumentException(
240
+ "auto_resume can only be True when on_timeout action is 'pause'."
241
+ )
242
+
243
+ if not keep_memory and auto_resume:
244
+ raise InvalidArgumentException(
245
+ "auto_resume: True is not a valid value when keep_memory: False - "
246
+ "a filesystem-only snapshot cannot be auto-resumed by traffic and "
247
+ "must be resumed explicitly using Sandbox.connect()."
248
+ )
249
+
250
+ network_body = build_network_config(network)
251
+ body = NewSandbox(
252
+ template_id=template,
253
+ auto_pause=on_timeout == "pause",
254
+ auto_pause_memory=keep_memory if on_timeout == "pause" else UNSET,
255
+ auto_resume=SandboxAutoResumeConfig(enabled=auto_resume),
256
+ metadata=metadata or {},
257
+ timeout=timeout,
258
+ env_vars=env_vars or {},
259
+ mcp=cast(Any, mcp) or UNSET,
260
+ secure=secure,
261
+ allow_internet_access=allow_internet_access,
262
+ network=SandboxNetworkConfig(**network_body) if network_body else UNSET,
263
+ volume_mounts=volume_mounts if volume_mounts else UNSET,
264
+ )
265
+
266
+ api_client = get_api_client(config)
267
+ res = await post_sandboxes.asyncio_detailed(
268
+ body=body,
269
+ client=api_client,
270
+ )
271
+
272
+ if res.status_code >= 300:
273
+ raise handle_api_exception(res)
274
+
275
+ if res.parsed is None:
276
+ raise Exception("Body of the request is None")
277
+
278
+ if isinstance(res.parsed, Error):
279
+ raise SandboxException(f"{res.parsed.message}: Request failed")
280
+
281
+ if Version(res.parsed.envd_version) < Version("0.1.0"):
282
+ await SandboxApi._cls_kill(res.parsed.sandbox_id)
283
+ raise TemplateException(
284
+ "You need to update the template to use the new SDK."
285
+ )
286
+
287
+ domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None
288
+ envd_token = (
289
+ res.parsed.envd_access_token
290
+ if isinstance(res.parsed.envd_access_token, str)
291
+ else None
292
+ )
293
+ traffic_token = (
294
+ res.parsed.traffic_access_token
295
+ if isinstance(res.parsed.traffic_access_token, str)
296
+ else None
297
+ )
298
+
299
+ return SandboxCreateResponse(
300
+ sandbox_id=res.parsed.sandbox_id,
301
+ sandbox_domain=domain,
302
+ envd_version=res.parsed.envd_version,
303
+ envd_access_token=envd_token,
304
+ traffic_access_token=traffic_token,
305
+ )
306
+
307
+ @classmethod
308
+ async def _cls_get_metrics(
309
+ cls,
310
+ sandbox_id: str,
311
+ start: Optional[datetime.datetime] = None,
312
+ end: Optional[datetime.datetime] = None,
313
+ **opts: Unpack[ApiParams],
314
+ ) -> List[SandboxMetrics]:
315
+ """
316
+ Get the metrics of the sandbox specified by sandbox ID.
317
+
318
+ :param sandbox_id: Sandbox ID
319
+ :param start: Start time for the metrics, defaults to the start of the sandbox
320
+ :param end: End time for the metrics, defaults to the current time
321
+
322
+ :return: List of sandbox metrics containing CPU, memory and disk usage information
323
+ """
324
+ config = ConnectionConfig(**opts)
325
+
326
+ if config.debug:
327
+ # Skip getting the metrics in debug mode
328
+ return []
329
+
330
+ api_client = get_api_client(config)
331
+ res = await get_sandboxes_sandbox_id_metrics.asyncio_detailed(
332
+ sandbox_id,
333
+ start=int(start.timestamp()) if start else UNSET,
334
+ end=int(end.timestamp()) if end else UNSET,
335
+ client=api_client,
336
+ )
337
+
338
+ if res.status_code == 404:
339
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
340
+
341
+ if res.status_code >= 300:
342
+ raise handle_api_exception(res)
343
+
344
+ if res.parsed is None:
345
+ return []
346
+
347
+ # Check if res.parse is Error
348
+ if isinstance(res.parsed, Error):
349
+ raise SandboxException(f"{res.parsed.message}: Request failed")
350
+
351
+ # Convert to typed SandboxMetrics objects
352
+ return [
353
+ SandboxMetrics(
354
+ cpu_count=metric.cpu_count,
355
+ cpu_used_pct=metric.cpu_used_pct,
356
+ disk_total=metric.disk_total,
357
+ disk_used=metric.disk_used,
358
+ mem_total=metric.mem_total,
359
+ mem_used=metric.mem_used,
360
+ mem_cache=metric.mem_cache,
361
+ timestamp=metric.timestamp,
362
+ )
363
+ for metric in res.parsed
364
+ ]
365
+
366
+ @classmethod
367
+ async def _cls_create_snapshot(
368
+ cls,
369
+ sandbox_id: str,
370
+ name: Optional[str] = None,
371
+ **opts: Unpack[ApiParams],
372
+ ) -> SnapshotInfo:
373
+ config = ConnectionConfig(**opts)
374
+
375
+ api_client = get_api_client(config)
376
+ res = await post_sandboxes_sandbox_id_snapshots.asyncio_detailed(
377
+ sandbox_id,
378
+ client=api_client,
379
+ body=PostSandboxesSandboxIDSnapshotsBody(name=name if name else UNSET),
380
+ )
381
+
382
+ if res.status_code == 404:
383
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
384
+
385
+ if res.status_code >= 300:
386
+ raise handle_api_exception(res)
387
+
388
+ if res.parsed is None:
389
+ raise Exception("Body of the request is None")
390
+
391
+ if isinstance(res.parsed, Error):
392
+ raise SandboxException(f"{res.parsed.message}: Request failed")
393
+
394
+ return SnapshotInfo(
395
+ snapshot_id=res.parsed.snapshot_id,
396
+ names=list(res.parsed.names) if res.parsed.names else [],
397
+ )
398
+
399
+ @classmethod
400
+ async def _cls_delete_snapshot(
401
+ cls,
402
+ snapshot_id: str,
403
+ **opts: Unpack[ApiParams],
404
+ ) -> bool:
405
+ config = ConnectionConfig(**opts)
406
+
407
+ api_client = get_api_client(config)
408
+ res = await delete_templates_template_id.asyncio_detailed(
409
+ snapshot_id,
410
+ client=api_client,
411
+ )
412
+
413
+ if res.status_code == 404:
414
+ return False
415
+
416
+ if res.status_code >= 300:
417
+ raise handle_api_exception(res)
418
+
419
+ return True
420
+
421
+ @classmethod
422
+ async def _cls_pause(
423
+ cls,
424
+ sandbox_id: str,
425
+ keep_memory: bool = True,
426
+ **opts: Unpack[ApiParams],
427
+ ) -> bool:
428
+ config = ConnectionConfig(**opts)
429
+
430
+ api_client = get_api_client(config)
431
+ res = await post_sandboxes_sandbox_id_pause.asyncio_detailed(
432
+ sandbox_id,
433
+ client=api_client,
434
+ body=SandboxPauseRequest(memory=keep_memory),
435
+ )
436
+
437
+ if res.status_code == 404:
438
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
439
+
440
+ if res.status_code == 409:
441
+ # Sandbox is already paused
442
+ return False
443
+
444
+ if res.status_code >= 300:
445
+ raise handle_api_exception(res)
446
+
447
+ # Check if res.parse is Error
448
+ if isinstance(res.parsed, Error):
449
+ raise SandboxException(f"{res.parsed.message}: Request failed")
450
+
451
+ return True
452
+
453
+ @classmethod
454
+ async def _cls_connect(
455
+ cls,
456
+ sandbox_id: str,
457
+ timeout: Optional[int] = None,
458
+ logger: Optional[logging.Logger] = None,
459
+ **opts: Unpack[ApiParams],
460
+ ) -> SandboxCreateResponse:
461
+ timeout = timeout or SandboxBase.default_sandbox_timeout
462
+
463
+ # Sandbox is not running, resume it
464
+ config = ConnectionConfig(logger=logger, **opts)
465
+
466
+ api_client = get_api_client(config)
467
+ res = await post_sandboxes_sandbox_id_connect.asyncio_detailed(
468
+ sandbox_id,
469
+ client=api_client,
470
+ body=ConnectSandbox(timeout=timeout),
471
+ )
472
+
473
+ if res.status_code == 404:
474
+ raise SandboxNotFoundException(f"Paused sandbox {sandbox_id} not found")
475
+
476
+ if res.status_code >= 300:
477
+ raise handle_api_exception(res)
478
+
479
+ # Check if res.parse is Error
480
+ if isinstance(res.parsed, Error):
481
+ raise SandboxException(f"{res.parsed.message}: Request failed")
482
+
483
+ if res.parsed is None:
484
+ raise Exception("Body of the request is None")
485
+
486
+ domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None
487
+ envd_token = (
488
+ res.parsed.envd_access_token
489
+ if isinstance(res.parsed.envd_access_token, str)
490
+ else None
491
+ )
492
+ traffic_token = (
493
+ res.parsed.traffic_access_token
494
+ if isinstance(res.parsed.traffic_access_token, str)
495
+ else None
496
+ )
497
+
498
+ return SandboxCreateResponse(
499
+ sandbox_id=res.parsed.sandbox_id,
500
+ sandbox_domain=domain,
501
+ envd_version=res.parsed.envd_version,
502
+ envd_access_token=envd_token,
503
+ traffic_access_token=traffic_token,
504
+ )
@@ -0,0 +1,7 @@
1
+ from typing import TypeVar, Union, Callable, Awaitable
2
+
3
+ T = TypeVar("T")
4
+ OutputHandler = Union[
5
+ Callable[[T], None],
6
+ Callable[[T], Awaitable[None]],
7
+ ]
@@ -0,0 +1,5 @@
1
+ SUPPORTED_SANDBOX_DOMAINS = ("vm.betmandu.net",)
2
+
3
+
4
+ def is_supported_sandbox_domain(sandbox_domain: str) -> bool:
5
+ return sandbox_domain in SUPPORTED_SANDBOX_DOMAINS