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 import handle_api_exception
7
+ from loopix.api.client.api.sandboxes import get_v2_sandboxes
8
+ from loopix.api.client.api.snapshots import get_snapshots
9
+ from loopix.api.client.models.error import Error
10
+ from loopix.api.client.types import UNSET
11
+ from loopix.connection_config import ApiParams, ConnectionConfig
12
+ from loopix.exceptions import SandboxException
13
+ from loopix.sandbox.sandbox_api import (
14
+ SandboxPaginatorBase,
15
+ SandboxInfo,
16
+ SnapshotPaginatorBase,
17
+ SnapshotInfo,
18
+ )
19
+ from loopix.api.client_sync import get_api_client
20
+
21
+
22
+ class SandboxPaginator(SandboxPaginatorBase):
23
+ """
24
+ Paginator for listing sandboxes.
25
+
26
+ Example:
27
+ ```python
28
+ paginator = Sandbox.list()
29
+
30
+ while paginator.has_next:
31
+ sandboxes = paginator.next_items()
32
+ print(sandboxes)
33
+ ```
34
+ """
35
+
36
+ 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 = get_v2_sandboxes.sync_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 SnapshotPaginator(SnapshotPaginatorBase):
86
+ """
87
+ Paginator for listing snapshots.
88
+
89
+ Example:
90
+ ```python
91
+ paginator = Sandbox.list_snapshots()
92
+
93
+ while paginator.has_next:
94
+ snapshots = paginator.next_items()
95
+ print(snapshots)
96
+ ```
97
+ """
98
+
99
+ 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 = get_snapshots.sync_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,491 @@
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.connection_config import ApiParams, ConnectionConfig
34
+ from loopix.exceptions import (
35
+ InvalidArgumentException,
36
+ SandboxException,
37
+ SandboxNotFoundException,
38
+ TemplateException,
39
+ )
40
+ from loopix.sandbox.main import SandboxBase
41
+ from loopix.sandbox.sandbox_api import (
42
+ build_network_update_body,
43
+ McpServer,
44
+ SandboxInfo,
45
+ SandboxLifecycle,
46
+ SandboxMetrics,
47
+ SandboxNetworkOpts,
48
+ SandboxNetworkUpdate,
49
+ SandboxQuery,
50
+ SnapshotInfo,
51
+ build_network_config,
52
+ )
53
+ from loopix.sandbox_sync.paginator import SandboxPaginator, get_api_client
54
+
55
+
56
+ class SandboxApi(SandboxBase):
57
+ @staticmethod
58
+ def list(
59
+ query: Optional[SandboxQuery] = None,
60
+ limit: Optional[int] = None,
61
+ next_token: Optional[str] = None,
62
+ **opts: Unpack[ApiParams],
63
+ ) -> SandboxPaginator:
64
+ """
65
+ List sandboxes.
66
+
67
+ By default (no `query.state` set), returns sandboxes in both `running`
68
+ and `paused` states. To filter by state, pass `query=SandboxQuery(state=[...])`.
69
+
70
+ :param query: Filter the list of sandboxes by metadata or state, e.g. `SandboxQuery(metadata={"key": "value"})` or `SandboxQuery(state=[SandboxState.RUNNING])`
71
+ :param limit: Maximum number of sandboxes to return per page
72
+ :param next_token: Token for pagination
73
+
74
+ :return: A `SandboxPaginator` that yields pages of sandboxes (running and paused by default). Iterate pages via `paginator.next_items()` while `paginator.has_next` is True.
75
+ """
76
+ return SandboxPaginator(
77
+ query=query,
78
+ limit=limit,
79
+ next_token=next_token,
80
+ **opts,
81
+ )
82
+
83
+ @classmethod
84
+ def _cls_get_info(
85
+ cls,
86
+ sandbox_id: str,
87
+ **opts: Unpack[ApiParams],
88
+ ) -> SandboxInfo:
89
+ """
90
+ Get the sandbox info.
91
+ :param sandbox_id: Sandbox ID
92
+
93
+ :return: Sandbox info
94
+ """
95
+ config = ConnectionConfig(**opts)
96
+
97
+ api_client = get_api_client(config)
98
+ res = get_sandboxes_sandbox_id.sync_detailed(
99
+ sandbox_id,
100
+ client=api_client,
101
+ )
102
+
103
+ if res.status_code == 404:
104
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
105
+
106
+ if res.status_code >= 300:
107
+ raise handle_api_exception(res)
108
+
109
+ if res.parsed is None:
110
+ raise Exception("Body of the request is None")
111
+
112
+ if isinstance(res.parsed, Error):
113
+ raise SandboxException(f"{res.parsed.message}: Request failed")
114
+
115
+ return SandboxInfo._from_sandbox_detail(res.parsed)
116
+
117
+ @classmethod
118
+ def _cls_kill(
119
+ cls,
120
+ sandbox_id: str,
121
+ **opts: Unpack[ApiParams],
122
+ ) -> bool:
123
+ config = ConnectionConfig(**opts)
124
+
125
+ if config.debug:
126
+ # Skip killing the sandbox in debug mode
127
+ return True
128
+
129
+ api_client = get_api_client(config)
130
+ res = delete_sandboxes_sandbox_id.sync_detailed(
131
+ sandbox_id,
132
+ client=api_client,
133
+ )
134
+
135
+ if res.status_code == 404:
136
+ return False
137
+
138
+ if res.status_code >= 300:
139
+ raise handle_api_exception(res)
140
+
141
+ return True
142
+
143
+ @classmethod
144
+ def _cls_set_timeout(
145
+ cls,
146
+ sandbox_id: str,
147
+ timeout: int,
148
+ **opts: Unpack[ApiParams],
149
+ ) -> None:
150
+ config = ConnectionConfig(**opts)
151
+
152
+ if config.debug:
153
+ # Skip setting timeout in debug mode
154
+ return
155
+
156
+ api_client = get_api_client(config)
157
+ res = post_sandboxes_sandbox_id_timeout.sync_detailed(
158
+ sandbox_id,
159
+ client=api_client,
160
+ body=PostSandboxesSandboxIDTimeoutBody(timeout=timeout),
161
+ )
162
+
163
+ if res.status_code == 404:
164
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
165
+
166
+ if res.status_code >= 300:
167
+ raise handle_api_exception(res)
168
+
169
+ @classmethod
170
+ def _cls_update_network(
171
+ cls,
172
+ sandbox_id: str,
173
+ network: SandboxNetworkUpdate,
174
+ **opts: Unpack[ApiParams],
175
+ ) -> None:
176
+ config = ConnectionConfig(**opts)
177
+
178
+ api_client = get_api_client(config)
179
+ res = put_sandboxes_sandbox_id_network.sync_detailed(
180
+ sandbox_id,
181
+ client=api_client,
182
+ body=build_network_update_body(network),
183
+ )
184
+
185
+ if res.status_code == 404:
186
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
187
+
188
+ if res.status_code >= 300:
189
+ raise handle_api_exception(res)
190
+
191
+ @classmethod
192
+ def _create_sandbox(
193
+ cls,
194
+ template: str,
195
+ timeout: int,
196
+ allow_internet_access: bool,
197
+ metadata: Optional[Dict[str, str]],
198
+ env_vars: Optional[Dict[str, str]],
199
+ secure: bool,
200
+ mcp: Optional[McpServer] = None,
201
+ network: Optional[SandboxNetworkOpts] = None,
202
+ lifecycle: Optional[SandboxLifecycle] = None,
203
+ volume_mounts: Optional[List[SandboxVolumeMountAPI]] = None,
204
+ logger: Optional[logging.Logger] = None,
205
+ **opts: Unpack[ApiParams],
206
+ ) -> SandboxCreateResponse:
207
+ config = ConnectionConfig(logger=logger, **opts)
208
+
209
+ # on_timeout accepts a bare action or {"action", "keep_memory"}; normalize.
210
+ # Only the object form carries keep_memory; anything else (a bare action
211
+ # string, or an unexpected value from an untyped caller) passes through as
212
+ # the action, so a non-"pause" value resolves to kill instead of crashing.
213
+ on_timeout_raw = lifecycle.get("on_timeout", "kill") if lifecycle else "kill"
214
+ if isinstance(on_timeout_raw, dict):
215
+ on_timeout = on_timeout_raw.get("action", "kill")
216
+ keep_memory_provided = "keep_memory" in on_timeout_raw
217
+ keep_memory = on_timeout_raw.get("keep_memory")
218
+ else:
219
+ on_timeout = on_timeout_raw
220
+ keep_memory = None
221
+ keep_memory_provided = False
222
+
223
+ # keep_memory only governs a pause action. The discriminated union type
224
+ # forbids it on action="kill"; re-check at runtime for callers that
225
+ # bypass the type.
226
+ if keep_memory_provided and on_timeout != "pause":
227
+ raise InvalidArgumentException(
228
+ "keep_memory is only allowed when on_timeout action is 'pause'."
229
+ )
230
+
231
+ # A missing or explicit None keep_memory defaults to True (full memory),
232
+ # mirroring the JS SDK; sending null would wrongly read as filesystem-only.
233
+ if keep_memory is None:
234
+ keep_memory = True
235
+ auto_resume = lifecycle.get("auto_resume", False) if lifecycle else False
236
+
237
+ if auto_resume and on_timeout != "pause":
238
+ raise InvalidArgumentException(
239
+ "auto_resume can only be True when on_timeout action is 'pause'."
240
+ )
241
+
242
+ if not keep_memory and auto_resume:
243
+ raise InvalidArgumentException(
244
+ "auto_resume: True is not a valid value when keep_memory: False - "
245
+ "a filesystem-only snapshot cannot be auto-resumed by traffic and "
246
+ "must be resumed explicitly using Sandbox.connect()."
247
+ )
248
+
249
+ network_body = build_network_config(network)
250
+ body = NewSandbox(
251
+ template_id=template,
252
+ auto_pause=on_timeout == "pause",
253
+ auto_pause_memory=keep_memory if on_timeout == "pause" else UNSET,
254
+ auto_resume=SandboxAutoResumeConfig(enabled=auto_resume),
255
+ metadata=metadata or {},
256
+ timeout=timeout,
257
+ env_vars=env_vars or {},
258
+ mcp=cast(Any, mcp) or UNSET,
259
+ secure=secure,
260
+ allow_internet_access=allow_internet_access,
261
+ network=SandboxNetworkConfig(**network_body) if network_body else UNSET,
262
+ volume_mounts=volume_mounts if volume_mounts else UNSET,
263
+ )
264
+
265
+ api_client = get_api_client(config)
266
+ res = post_sandboxes.sync_detailed(
267
+ body=body,
268
+ client=api_client,
269
+ )
270
+
271
+ if res.status_code >= 300:
272
+ raise handle_api_exception(res)
273
+
274
+ if res.parsed is None:
275
+ raise Exception("Body of the request is None")
276
+
277
+ if isinstance(res.parsed, Error):
278
+ raise SandboxException(f"{res.parsed.message}: Request failed")
279
+
280
+ if Version(res.parsed.envd_version) < Version("0.1.0"):
281
+ SandboxApi._cls_kill(res.parsed.sandbox_id)
282
+ raise TemplateException(
283
+ "You need to update the template to use the new SDK."
284
+ )
285
+
286
+ domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None
287
+ envd_token = (
288
+ res.parsed.envd_access_token
289
+ if isinstance(res.parsed.envd_access_token, str)
290
+ else None
291
+ )
292
+ traffic_token = (
293
+ res.parsed.traffic_access_token
294
+ if isinstance(res.parsed.traffic_access_token, str)
295
+ else None
296
+ )
297
+
298
+ return SandboxCreateResponse(
299
+ sandbox_id=res.parsed.sandbox_id,
300
+ sandbox_domain=domain,
301
+ envd_version=res.parsed.envd_version,
302
+ envd_access_token=envd_token,
303
+ traffic_access_token=traffic_token,
304
+ )
305
+
306
+ @classmethod
307
+ def _cls_get_metrics(
308
+ cls,
309
+ sandbox_id: str,
310
+ start: Optional[datetime.datetime] = None,
311
+ end: Optional[datetime.datetime] = None,
312
+ **opts: Unpack[ApiParams],
313
+ ) -> List[SandboxMetrics]:
314
+ config = ConnectionConfig(**opts)
315
+
316
+ if config.debug:
317
+ # Skip getting the metrics in debug mode
318
+ return []
319
+
320
+ api_client = get_api_client(config)
321
+ res = get_sandboxes_sandbox_id_metrics.sync_detailed(
322
+ sandbox_id,
323
+ start=int(start.timestamp()) if start else UNSET,
324
+ end=int(end.timestamp()) if end else UNSET,
325
+ client=api_client,
326
+ )
327
+
328
+ if res.status_code == 404:
329
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
330
+
331
+ if res.status_code >= 300:
332
+ raise handle_api_exception(res)
333
+
334
+ if res.parsed is None:
335
+ return []
336
+
337
+ if isinstance(res.parsed, Error):
338
+ raise SandboxException(f"{res.parsed.message}: Request failed")
339
+
340
+ # Convert to typed SandboxMetrics objects
341
+ return [
342
+ SandboxMetrics(
343
+ cpu_count=metric.cpu_count,
344
+ cpu_used_pct=metric.cpu_used_pct,
345
+ disk_total=metric.disk_total,
346
+ disk_used=metric.disk_used,
347
+ mem_total=metric.mem_total,
348
+ mem_used=metric.mem_used,
349
+ mem_cache=metric.mem_cache,
350
+ timestamp=metric.timestamp,
351
+ )
352
+ for metric in res.parsed
353
+ ]
354
+
355
+ @classmethod
356
+ def _cls_connect(
357
+ cls,
358
+ sandbox_id: str,
359
+ timeout: Optional[int] = None,
360
+ logger: Optional[logging.Logger] = None,
361
+ **opts: Unpack[ApiParams],
362
+ ) -> SandboxCreateResponse:
363
+ timeout = timeout or SandboxBase.default_sandbox_timeout
364
+
365
+ config = ConnectionConfig(logger=logger, **opts)
366
+
367
+ api_client = get_api_client(config)
368
+ res = post_sandboxes_sandbox_id_connect.sync_detailed(
369
+ sandbox_id,
370
+ client=api_client,
371
+ body=ConnectSandbox(timeout=timeout),
372
+ )
373
+
374
+ if res.status_code == 404:
375
+ raise SandboxNotFoundException(f"Paused sandbox {sandbox_id} not found")
376
+
377
+ if res.status_code >= 300:
378
+ raise handle_api_exception(res)
379
+
380
+ if isinstance(res.parsed, Error):
381
+ raise SandboxException(f"{res.parsed.message}: Request failed")
382
+
383
+ if res.parsed is None:
384
+ raise Exception("Body of the request is None")
385
+
386
+ domain = res.parsed.domain if isinstance(res.parsed.domain, str) else None
387
+ envd_token = (
388
+ res.parsed.envd_access_token
389
+ if isinstance(res.parsed.envd_access_token, str)
390
+ else None
391
+ )
392
+ traffic_token = (
393
+ res.parsed.traffic_access_token
394
+ if isinstance(res.parsed.traffic_access_token, str)
395
+ else None
396
+ )
397
+
398
+ return SandboxCreateResponse(
399
+ sandbox_id=res.parsed.sandbox_id,
400
+ sandbox_domain=domain,
401
+ envd_version=res.parsed.envd_version,
402
+ envd_access_token=envd_token,
403
+ traffic_access_token=traffic_token,
404
+ )
405
+
406
+ @classmethod
407
+ def _cls_create_snapshot(
408
+ cls,
409
+ sandbox_id: str,
410
+ name: Optional[str] = None,
411
+ **opts: Unpack[ApiParams],
412
+ ) -> SnapshotInfo:
413
+ config = ConnectionConfig(**opts)
414
+
415
+ api_client = get_api_client(config)
416
+ res = post_sandboxes_sandbox_id_snapshots.sync_detailed(
417
+ sandbox_id,
418
+ client=api_client,
419
+ body=PostSandboxesSandboxIDSnapshotsBody(name=name if name else UNSET),
420
+ )
421
+
422
+ if res.status_code == 404:
423
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
424
+
425
+ if res.status_code >= 300:
426
+ raise handle_api_exception(res)
427
+
428
+ if res.parsed is None:
429
+ raise Exception("Body of the request is None")
430
+
431
+ if isinstance(res.parsed, Error):
432
+ raise SandboxException(f"{res.parsed.message}: Request failed")
433
+
434
+ return SnapshotInfo(
435
+ snapshot_id=res.parsed.snapshot_id,
436
+ names=list(res.parsed.names) if res.parsed.names else [],
437
+ )
438
+
439
+ @classmethod
440
+ def _cls_delete_snapshot(
441
+ cls,
442
+ snapshot_id: str,
443
+ **opts: Unpack[ApiParams],
444
+ ) -> bool:
445
+ config = ConnectionConfig(**opts)
446
+
447
+ api_client = get_api_client(config)
448
+ res = delete_templates_template_id.sync_detailed(
449
+ snapshot_id,
450
+ client=api_client,
451
+ )
452
+
453
+ if res.status_code == 404:
454
+ return False
455
+
456
+ if res.status_code >= 300:
457
+ raise handle_api_exception(res)
458
+
459
+ return True
460
+
461
+ @classmethod
462
+ def _cls_pause(
463
+ cls,
464
+ sandbox_id: str,
465
+ keep_memory: bool = True,
466
+ **opts: Unpack[ApiParams],
467
+ ) -> bool:
468
+ config = ConnectionConfig(**opts)
469
+
470
+ api_client = get_api_client(config)
471
+ res = post_sandboxes_sandbox_id_pause.sync_detailed(
472
+ sandbox_id,
473
+ client=api_client,
474
+ body=SandboxPauseRequest(memory=keep_memory),
475
+ )
476
+
477
+ if res.status_code == 404:
478
+ raise SandboxNotFoundException(f"Sandbox {sandbox_id} not found")
479
+
480
+ if res.status_code == 409:
481
+ # Sandbox is already paused
482
+ return False
483
+
484
+ if res.status_code >= 300:
485
+ raise handle_api_exception(res)
486
+
487
+ # Check if res.parse is Error
488
+ if isinstance(res.parsed, Error):
489
+ raise SandboxException(f"{res.parsed.message}: Request failed")
490
+
491
+ return True
@@ -0,0 +1,45 @@
1
+ """
2
+ Special step name for the finalization phase of template building.
3
+ This is the last step that runs after all user-defined instructions.
4
+ """
5
+
6
+ FINALIZE_STEP_NAME = "finalize"
7
+
8
+ """
9
+ Special step name for the base image phase of template building.
10
+ This is the first step that sets up the base image.
11
+ """
12
+ BASE_STEP_NAME = "base"
13
+
14
+ """
15
+ Stack trace depth for capturing caller information.
16
+
17
+ Depth levels:
18
+ 1. TemplateClass
19
+ 2. Caller method (e.g., copy(), from_image(), etc.)
20
+
21
+ This depth is used to determine the original caller's location
22
+ for stack traces.
23
+ """
24
+ STACK_TRACE_DEPTH = 2
25
+
26
+ """
27
+ Default setting for whether to resolve symbolic links when copying files.
28
+ When False, symlinks are copied as symlinks rather than following them.
29
+ """
30
+ RESOLVE_SYMLINKS = False
31
+
32
+ """
33
+ Default setting for whether to gzip files when copying them into the
34
+ template. When True, the upload archive is gzipped before being uploaded.
35
+ """
36
+ GZIP = True
37
+
38
+ """
39
+ Default timeout (in seconds) for uploading the build-context archive to the
40
+ S3 presigned URL. Uploads of large archives can take far longer than the 60s
41
+ general API request timeout, so the upload uses a 1-hour default unless the
42
+ caller passes an explicit ``request_timeout``. This matches the JS SDK's
43
+ ``FILE_UPLOAD_TIMEOUT_MS``.
44
+ """
45
+ FILE_UPLOAD_TIMEOUT_SECONDS = 3600