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,8 @@
1
+ """
2
+ Network configuration helpers for Loopix sandboxes.
3
+ """
4
+
5
+ """
6
+ CIDR range that represents all traffic.
7
+ """
8
+ ALL_TRAFFIC = "0.0.0.0/0"
@@ -0,0 +1,624 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from typing import (
4
+ Any,
5
+ Callable,
6
+ Dict,
7
+ List,
8
+ Literal,
9
+ Mapping,
10
+ Optional,
11
+ TypedDict,
12
+ Union,
13
+ cast,
14
+ )
15
+
16
+ from typing_extensions import NotRequired, Unpack
17
+
18
+ from loopix.api.client.models import (
19
+ ListedSandbox,
20
+ SandboxDetail,
21
+ SandboxState,
22
+ )
23
+ from loopix.api.client.models import (
24
+ SandboxLifecycle as ClientSandboxLifecycle,
25
+ )
26
+ from loopix.api.client.models import (
27
+ SandboxNetworkConfig as ClientSandboxNetworkConfig,
28
+ )
29
+ from loopix.api.client.models import (
30
+ SandboxNetworkConfigRules,
31
+ )
32
+ from loopix.api.client.models import (
33
+ SandboxNetworkRule as ClientSandboxNetworkRule,
34
+ )
35
+ from loopix.api.client.models import (
36
+ SandboxNetworkTransform as ClientSandboxNetworkTransform,
37
+ )
38
+ from loopix.api.client.models import (
39
+ SandboxNetworkTransformHeaders as ClientSandboxNetworkTransformHeaders,
40
+ )
41
+ from loopix.api.client.models import (
42
+ SandboxNetworkUpdateConfig,
43
+ )
44
+ from loopix.api.client.models import (
45
+ SandboxNetworkUpdateConfigRules,
46
+ )
47
+ from loopix.api.client.types import Unset
48
+ from loopix.connection_config import ApiParams
49
+ from loopix.sandbox.mcp import McpServer as BaseMcpServer
50
+ from loopix.sandbox.network import ALL_TRAFFIC
51
+ from loopix.paginator import PaginatorBase
52
+
53
+
54
+ class GitHubMcpServerConfig(TypedDict):
55
+ """
56
+ Configuration for a GitHub-based MCP server.
57
+ """
58
+
59
+ run_cmd: str
60
+ """
61
+ Command to run the MCP server. Must start a stdio-compatible server.
62
+ """
63
+ install_cmd: NotRequired[str]
64
+ """
65
+ Command to install dependencies for the MCP server. Working directory is the root of the github repository.
66
+ """
67
+ envs: NotRequired[Dict[str, str]]
68
+ """
69
+ Environment variables to set in the MCP process.
70
+ """
71
+
72
+
73
+ # Extended MCP server configuration that includes base servers
74
+ # and allows dynamic GitHub-based MCP servers with custom run and install commands.
75
+ # For GitHub servers, use keys in the format "github/owner/repo"
76
+ GitHubMcpServer = Dict[str, Union[GitHubMcpServerConfig, Any]]
77
+
78
+ # Union type that combines base MCP servers with GitHub-based servers
79
+ McpServer = Union[BaseMcpServer, GitHubMcpServer]
80
+
81
+
82
+ class SandboxNetworkTransform(TypedDict):
83
+ """
84
+ Transform applied to egress requests matching a :class:`SandboxNetworkRule`.
85
+ """
86
+
87
+ headers: NotRequired[Dict[str, str]]
88
+ """
89
+ Headers to inject into the outbound request. Values override any headers
90
+ already present on the request.
91
+ """
92
+
93
+
94
+ class SandboxNetworkRule(TypedDict):
95
+ """
96
+ Per-domain rule applied to egress requests.
97
+ """
98
+
99
+ transform: NotRequired[SandboxNetworkTransform]
100
+ """
101
+ Transform applied to requests matching this rule.
102
+ """
103
+
104
+
105
+ SandboxNetworkRules = Dict[str, List[SandboxNetworkRule]]
106
+ """
107
+ Map of host (or CIDR / IP) to ordered list of rules applied to outbound
108
+ requests for that host. Registering a host here does not allow egress on its
109
+ own — the host must also appear in ``SandboxNetworkOpts.allow_out``.
110
+ """
111
+
112
+
113
+ class SandboxNetworkRuleInfo(TypedDict):
114
+ """
115
+ Per-domain rule as returned by the sandbox info endpoint. Mirrors
116
+ :class:`SandboxNetworkRule` but with ``transform`` always materialized to
117
+ the static :class:`SandboxNetworkTransform` shape — no callable variant.
118
+ """
119
+
120
+ transform: NotRequired[SandboxNetworkTransform]
121
+
122
+
123
+ @dataclass(frozen=True)
124
+ class SandboxNetworkSelectorContext:
125
+ """
126
+ Context passed to ``allow_out``/``deny_out`` callables.
127
+ """
128
+
129
+ all_traffic: str
130
+ """All traffic sentinel — equivalent to ``"0.0.0.0/0"``."""
131
+
132
+ rules: Mapping[str, List[SandboxNetworkRule]]
133
+ """Rules registered in :attr:`SandboxNetworkOpts.rules`."""
134
+
135
+
136
+ SandboxNetworkSelector = Union[
137
+ List[str],
138
+ Callable[[SandboxNetworkSelectorContext], List[str]],
139
+ ]
140
+ """
141
+ Egress rule list, either a static list of CIDR blocks / IP addresses /
142
+ hostnames, or a callable that receives a :class:`SandboxNetworkSelectorContext`
143
+ and returns the same.
144
+ """
145
+
146
+
147
+ class SandboxNetworkOpts(TypedDict):
148
+ """
149
+ Sandbox network configuration options.
150
+ """
151
+
152
+ allow_out: NotRequired[SandboxNetworkSelector]
153
+ """
154
+ Allow outbound traffic from the sandbox to the specified addresses.
155
+ If ``allow_out`` is not specified, all outbound traffic is allowed.
156
+
157
+ Accepts either a static list of CIDR blocks / IP addresses / hostnames, or
158
+ a callable that receives a :class:`SandboxNetworkSelectorContext` and
159
+ returns the same. ``ctx.all_traffic`` is ``"0.0.0.0/0"``; ``ctx.rules`` is
160
+ a read-only view of :attr:`rules`.
161
+
162
+ Examples:
163
+ - Static list: ``["1.1.1.1", "8.8.8.0/24"]``
164
+ - Allow only rule-registered hosts:
165
+ ``lambda ctx: list(ctx.rules.keys())``
166
+ """
167
+
168
+ deny_out: NotRequired[SandboxNetworkSelector]
169
+ """
170
+ Deny outbound traffic from the sandbox to the specified addresses.
171
+
172
+ Accepts the same shapes as ``allow_out``.
173
+
174
+ Examples:
175
+ - Static list: ``["1.1.1.1", "8.8.8.0/24"]``
176
+ - Block all egress: ``lambda ctx: [ctx.all_traffic]``
177
+ """
178
+
179
+ rules: NotRequired[SandboxNetworkRules]
180
+ """
181
+ Per-domain transform rules applied to matching egress HTTP/HTTPS
182
+ requests. Keys are domains (e.g. ``"api.example.com"``); values are
183
+ ordered lists of :class:`SandboxNetworkRule`.
184
+
185
+ Registering a host here does not allow egress on its own — the host must
186
+ also appear in ``allow_out``. Hosts registered here are exposed to the
187
+ ``allow_out``/``deny_out`` callables via ``ctx.rules``.
188
+ """
189
+
190
+ allow_public_traffic: NotRequired[bool]
191
+ """
192
+ Controls whether sandbox URLs should be publicly accessible or require authentication.
193
+ Defaults to True.
194
+ """
195
+
196
+ mask_request_host: NotRequired[str]
197
+ """
198
+ Allows specifying a custom host mask for all sandbox requests.
199
+ Supports ${PORT} variable. Defaults to "vm.betmandu.net/sandbox/sandboxid/${PORT}".
200
+
201
+ Examples:
202
+ - Custom subdomain: `"${PORT}-myapp.example.com"`
203
+ """
204
+
205
+
206
+ class SandboxNetworkUpdate(TypedDict, total=False):
207
+ """
208
+ Subset of :class:`SandboxNetworkOpts` accepted by ``Sandbox.update_network``.
209
+ The update endpoint replaces all egress rules atomically — fields that are
210
+ omitted are cleared on the server.
211
+ """
212
+
213
+ allow_out: SandboxNetworkSelector
214
+ """See :attr:`SandboxNetworkOpts.allow_out`."""
215
+
216
+ deny_out: SandboxNetworkSelector
217
+ """See :attr:`SandboxNetworkOpts.deny_out`."""
218
+
219
+ rules: SandboxNetworkRules
220
+ """See :attr:`SandboxNetworkOpts.rules`."""
221
+
222
+ allow_internet_access: bool
223
+ """
224
+ Allow sandbox to access the internet. When set to ``False``, it behaves the
225
+ same as specifying ``deny_out=["0.0.0.0/0"]`` in the network config.
226
+ """
227
+
228
+
229
+ class SandboxNetworkInfo(TypedDict, total=False):
230
+ """
231
+ Network configuration as returned by the sandbox info endpoint.
232
+ Mirrors :class:`SandboxNetworkOpts` but with ``allow_out``/``deny_out``
233
+ always materialized to plain string lists.
234
+ """
235
+
236
+ allow_out: List[str]
237
+ deny_out: List[str]
238
+ rules: Dict[str, List[SandboxNetworkRuleInfo]]
239
+ allow_public_traffic: bool
240
+ mask_request_host: str
241
+
242
+
243
+ class SandboxOnTimeoutPause(TypedDict):
244
+ """
245
+ Object form of `on_timeout` that auto-pauses the sandbox when the timeout is
246
+ reached, optionally controlling the pause snapshot kind via `keep_memory`.
247
+ """
248
+
249
+ action: Literal["pause"]
250
+ """Auto-pause the sandbox when the timeout is reached."""
251
+
252
+ keep_memory: NotRequired[bool]
253
+ """
254
+ Whether the timeout auto-pause keeps a full memory snapshot. Defaults to `True`.
255
+ When `False`, the auto-pause drops the in-memory state and persists only the
256
+ filesystem (a filesystem-only snapshot); resuming such a sandbox cold-boots
257
+ (reboots) it from disk, losing running processes and open connections.
258
+
259
+ Cannot be combined with `auto_resume`: auto-resume wakes a paused sandbox on
260
+ inbound traffic by restoring its memory snapshot in place, so the request that
261
+ woke it hits an already-running process. A filesystem-only snapshot has no
262
+ memory to restore — resuming cold-boots it — so it can't be woken transparently
263
+ by traffic and must be resumed explicitly via `connect()`.
264
+ """
265
+
266
+
267
+ class SandboxOnTimeoutKill(TypedDict):
268
+ """
269
+ Object form of `on_timeout` that kills the sandbox when the timeout is reached.
270
+ """
271
+
272
+ action: Literal["kill"]
273
+ """Kill the sandbox when the timeout is reached."""
274
+
275
+
276
+ SandboxOnTimeout = Union[
277
+ Literal["pause", "kill"], SandboxOnTimeoutPause, SandboxOnTimeoutKill
278
+ ]
279
+ """
280
+ What should happen to the sandbox when the timeout is reached. Either the bare
281
+ action (`"pause"` / `"kill"`) or the object form. The object form is a
282
+ discriminated union on `action`: `keep_memory` is only accepted alongside
283
+ `action: "pause"`. Passing `keep_memory` with `action: "kill"` is a static type
284
+ error.
285
+ """
286
+
287
+
288
+ class SandboxLifecycle(TypedDict):
289
+ """
290
+ Sandbox lifecycle configuration; defines post-timeout behavior and auto-resume settings.
291
+ Defaults to `on_timeout="kill"` and `auto_resume=False`.
292
+ """
293
+
294
+ on_timeout: SandboxOnTimeout
295
+ """
296
+ What should happen to the sandbox when timeout is reached. `"kill"` terminates
297
+ the sandbox; `"pause"` pauses it for later resume. Accepts either the bare
298
+ action or an object `{"action": "pause", "keep_memory": ...}` /
299
+ `{"action": "kill"}` to also control the pause snapshot kind. Defaults to
300
+ `"kill"`.
301
+ """
302
+
303
+ auto_resume: NotRequired[bool]
304
+ """
305
+ Whether activity should cause the sandbox to resume when paused. Defaults to `False`.
306
+ Can be `True` only when `on_timeout` is `pause`. Not supported when
307
+ `keep_memory` is `False` (a filesystem-only snapshot must be resumed
308
+ explicitly via `connect()`).
309
+ """
310
+
311
+
312
+ class SandboxInfoLifecycle(TypedDict):
313
+ """
314
+ Sandbox lifecycle configuration returned by sandbox info.
315
+ """
316
+
317
+ on_timeout: Literal["pause", "kill"]
318
+ """
319
+ What should happen to the sandbox when timeout is reached.
320
+ """
321
+
322
+ auto_resume: bool
323
+ """
324
+ Whether activity should cause the sandbox to resume when paused.
325
+ """
326
+
327
+
328
+ def _resolve_network_selector(
329
+ selector: Optional[SandboxNetworkSelector],
330
+ rules: Mapping[str, List[SandboxNetworkRule]],
331
+ ) -> Optional[List[str]]:
332
+ if selector is None:
333
+ return None
334
+
335
+ if callable(selector):
336
+ ctx = SandboxNetworkSelectorContext(all_traffic=ALL_TRAFFIC, rules=rules)
337
+ return list(selector(ctx))
338
+
339
+ return list(selector)
340
+
341
+
342
+ def _build_client_rules(rules: SandboxNetworkRules) -> SandboxNetworkConfigRules:
343
+ client_rules = SandboxNetworkConfigRules()
344
+ for host, host_rules in rules.items():
345
+ converted: List[ClientSandboxNetworkRule] = []
346
+ for rule in host_rules:
347
+ transform = rule.get("transform")
348
+ if transform is None:
349
+ converted.append(ClientSandboxNetworkRule())
350
+ continue
351
+
352
+ client_transform = ClientSandboxNetworkTransform()
353
+ headers = transform.get("headers")
354
+ if headers:
355
+ client_headers = ClientSandboxNetworkTransformHeaders()
356
+ client_headers.additional_properties = dict(headers)
357
+ client_transform.headers = client_headers
358
+
359
+ converted.append(ClientSandboxNetworkRule(transform=client_transform))
360
+ client_rules.additional_properties[host] = converted
361
+
362
+ return client_rules
363
+
364
+
365
+ def _build_network_egress(
366
+ network: Mapping[str, Any],
367
+ ) -> Dict[str, Any]:
368
+ """
369
+ Resolve the shared egress fields (``allow_out`` / ``deny_out`` / per-host
370
+ ``rules``) used by both the create and update endpoints. ``rules`` in the
371
+ returned dict is the inner ``Dict[host, List[ClientSandboxNetworkRule]]``
372
+ — callers wrap it in their endpoint-specific rules attrs class.
373
+ """
374
+ rules = network.get("rules") or {}
375
+ allow_out = _resolve_network_selector(network.get("allow_out"), rules)
376
+ deny_out = _resolve_network_selector(network.get("deny_out"), rules)
377
+
378
+ body: Dict[str, Any] = {}
379
+ if allow_out is not None:
380
+ body["allow_out"] = allow_out
381
+ if deny_out is not None:
382
+ body["deny_out"] = deny_out
383
+ if "rules" in network and network["rules"] is not None:
384
+ body["rules"] = _build_client_rules(network["rules"]).additional_properties
385
+
386
+ return body
387
+
388
+
389
+ def build_network_config(
390
+ network: Optional[SandboxNetworkOpts],
391
+ ) -> Optional[Dict[str, Any]]:
392
+ """Resolve a :class:`SandboxNetworkOpts` into the dict the API expects."""
393
+ if network is None:
394
+ return None
395
+
396
+ body = _build_network_egress(network)
397
+ if "rules" in body:
398
+ client_rules = SandboxNetworkConfigRules()
399
+ client_rules.additional_properties = body["rules"]
400
+ body["rules"] = client_rules
401
+ if "allow_public_traffic" in network:
402
+ body["allow_public_traffic"] = network["allow_public_traffic"]
403
+ if "mask_request_host" in network:
404
+ body["mask_request_host"] = network["mask_request_host"]
405
+
406
+ return body
407
+
408
+
409
+ def build_network_update_body(
410
+ network: SandboxNetworkUpdate,
411
+ ) -> SandboxNetworkUpdateConfig:
412
+ """Resolve a :class:`SandboxNetworkUpdate` into the API client body."""
413
+ egress = _build_network_egress(network)
414
+
415
+ body = SandboxNetworkUpdateConfig()
416
+ if "allow_out" in egress:
417
+ body.allow_out = egress["allow_out"]
418
+ if "deny_out" in egress:
419
+ body.deny_out = egress["deny_out"]
420
+ if "rules" in egress:
421
+ rules = SandboxNetworkUpdateConfigRules()
422
+ rules.additional_properties = egress["rules"]
423
+ body.rules = rules
424
+ if "allow_internet_access" in network:
425
+ body.allow_internet_access = network["allow_internet_access"]
426
+
427
+ return body
428
+
429
+
430
+ def from_client_network_config(
431
+ network: Union[Unset, ClientSandboxNetworkConfig],
432
+ ) -> Optional[SandboxNetworkInfo]:
433
+ if isinstance(network, Unset):
434
+ return None
435
+
436
+ result: SandboxNetworkInfo = {}
437
+
438
+ if not isinstance(network.allow_out, Unset):
439
+ result["allow_out"] = list(network.allow_out)
440
+ if not isinstance(network.deny_out, Unset):
441
+ result["deny_out"] = list(network.deny_out)
442
+ if not isinstance(network.rules, Unset):
443
+ result["rules"] = cast(
444
+ Dict[str, List[SandboxNetworkRuleInfo]], network.rules.to_dict()
445
+ )
446
+ if not isinstance(network.allow_public_traffic, Unset):
447
+ result["allow_public_traffic"] = network.allow_public_traffic
448
+ if not isinstance(network.mask_request_host, Unset):
449
+ result["mask_request_host"] = network.mask_request_host
450
+
451
+ return result
452
+
453
+
454
+ def from_client_lifecycle(
455
+ lifecycle: Union[Unset, ClientSandboxLifecycle],
456
+ ) -> Optional[SandboxInfoLifecycle]:
457
+ if isinstance(lifecycle, Unset):
458
+ return None
459
+
460
+ result: SandboxInfoLifecycle = {
461
+ "on_timeout": cast(Literal["pause", "kill"], lifecycle.on_timeout),
462
+ "auto_resume": lifecycle.auto_resume,
463
+ }
464
+
465
+ return result
466
+
467
+
468
+ @dataclass
469
+ class SandboxInfo:
470
+ """Information about a sandbox."""
471
+
472
+ sandbox_id: str
473
+ """Sandbox ID."""
474
+ sandbox_domain: Optional[str]
475
+ """Domain where the sandbox is hosted."""
476
+ template_id: str
477
+ """Template ID."""
478
+ name: Optional[str]
479
+ """Template name."""
480
+ metadata: Dict[str, str]
481
+ """Saved sandbox metadata."""
482
+ started_at: datetime
483
+ """Sandbox start time."""
484
+ end_at: datetime
485
+ """Sandbox expiration date."""
486
+ state: SandboxState
487
+ """Sandbox state."""
488
+ cpu_count: int
489
+ """Sandbox CPU count."""
490
+ memory_mb: int
491
+ """Sandbox Memory size in MiB."""
492
+ envd_version: str
493
+ """Envd version."""
494
+ allow_internet_access: Optional[bool] = None
495
+ """Whether internet access was explicitly enabled or disabled for the sandbox."""
496
+ network: Optional[SandboxNetworkInfo] = None
497
+ """Sandbox network configuration."""
498
+ lifecycle: Optional[SandboxInfoLifecycle] = None
499
+ """Sandbox lifecycle configuration."""
500
+ volume_mounts: List[Dict[str, str]] = field(default_factory=list)
501
+ """Volume mounts for the sandbox."""
502
+
503
+ @classmethod
504
+ def _from_sandbox_data(
505
+ cls,
506
+ sandbox: Union[ListedSandbox, SandboxDetail],
507
+ sandbox_domain: Optional[str] = None,
508
+ allow_internet_access: Optional[bool] = None,
509
+ network: Optional[SandboxNetworkInfo] = None,
510
+ lifecycle: Optional[SandboxInfoLifecycle] = None,
511
+ ):
512
+ return cls(
513
+ sandbox_domain=sandbox_domain,
514
+ sandbox_id=sandbox.sandbox_id,
515
+ template_id=sandbox.template_id,
516
+ name=(sandbox.alias if isinstance(sandbox.alias, str) else None),
517
+ metadata=cast(
518
+ Dict[str, str],
519
+ sandbox.metadata if isinstance(sandbox.metadata, dict) else {},
520
+ ),
521
+ started_at=sandbox.started_at,
522
+ end_at=sandbox.end_at,
523
+ state=sandbox.state,
524
+ cpu_count=sandbox.cpu_count,
525
+ memory_mb=sandbox.memory_mb,
526
+ envd_version=sandbox.envd_version,
527
+ volume_mounts=[
528
+ {"name": vm.name, "path": vm.path} for vm in sandbox.volume_mounts
529
+ ]
530
+ if not isinstance(sandbox.volume_mounts, Unset)
531
+ else [],
532
+ allow_internet_access=allow_internet_access,
533
+ network=network,
534
+ lifecycle=lifecycle,
535
+ )
536
+
537
+ @classmethod
538
+ def _from_listed_sandbox(cls, listed_sandbox: ListedSandbox):
539
+ return cls._from_sandbox_data(listed_sandbox)
540
+
541
+ @classmethod
542
+ def _from_sandbox_detail(cls, sandbox_detail: SandboxDetail):
543
+ return cls._from_sandbox_data(
544
+ sandbox_detail,
545
+ sandbox_domain=(
546
+ sandbox_detail.domain
547
+ if isinstance(sandbox_detail.domain, str)
548
+ else None
549
+ ),
550
+ allow_internet_access=(
551
+ sandbox_detail.allow_internet_access
552
+ if isinstance(sandbox_detail.allow_internet_access, bool)
553
+ else None
554
+ ),
555
+ network=from_client_network_config(sandbox_detail.network),
556
+ lifecycle=from_client_lifecycle(sandbox_detail.lifecycle),
557
+ )
558
+
559
+
560
+ @dataclass
561
+ class SandboxQuery:
562
+ """Query parameters for listing sandboxes."""
563
+
564
+ metadata: Optional[dict[str, str]] = None
565
+ """Filter sandboxes by metadata."""
566
+
567
+ state: Optional[list[SandboxState]] = None
568
+ """Filter sandboxes by state."""
569
+
570
+
571
+ @dataclass
572
+ class SandboxMetrics:
573
+ """Sandbox metrics."""
574
+
575
+ cpu_count: int
576
+ """Number of CPUs."""
577
+ cpu_used_pct: float
578
+ """CPU usage percentage."""
579
+ disk_total: int
580
+ """Total disk space in bytes."""
581
+ disk_used: int
582
+ """Disk used in bytes."""
583
+ mem_total: int
584
+ """Total memory in bytes."""
585
+ mem_used: int
586
+ """Memory used in bytes."""
587
+ mem_cache: int
588
+ """Cached memory (page cache) in bytes."""
589
+ timestamp: datetime
590
+ """Timestamp of the metric entry."""
591
+
592
+
593
+ @dataclass
594
+ class SnapshotInfo:
595
+ """Information about a snapshot."""
596
+
597
+ snapshot_id: str
598
+ """Snapshot identifier — template ID with tag, or namespaced name with tag (e.g. my-snapshot:latest). Can be used with Sandbox.create() to create a new sandbox from this snapshot."""
599
+ names: List[str] = field(default_factory=list)
600
+ """Full names of the snapshot template including team namespace and tag (e.g. team-slug/my-snapshot:v2)."""
601
+
602
+
603
+ class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]):
604
+ def __init__(
605
+ self,
606
+ sandbox_id: Optional[str] = None,
607
+ limit: Optional[int] = None,
608
+ next_token: Optional[str] = None,
609
+ **opts: Unpack[ApiParams],
610
+ ):
611
+ super().__init__(limit=limit, next_token=next_token, **opts)
612
+ self.sandbox_id = sandbox_id
613
+
614
+
615
+ class SandboxPaginatorBase(PaginatorBase[SandboxInfo, ApiParams]):
616
+ def __init__(
617
+ self,
618
+ query: Optional[SandboxQuery] = None,
619
+ limit: Optional[int] = None,
620
+ next_token: Optional[str] = None,
621
+ **opts: Unpack[ApiParams],
622
+ ):
623
+ super().__init__(limit=limit, next_token=next_token, **opts)
624
+ self.query = query
@@ -0,0 +1,47 @@
1
+ import base64
2
+ import hashlib
3
+ import time
4
+
5
+ from typing import Optional, TypedDict, Literal
6
+
7
+ Operation = Literal["read", "write"]
8
+
9
+
10
+ class Signature(TypedDict):
11
+ signature: str
12
+ expiration: Optional[int] # Unix timestamp or None
13
+
14
+
15
+ def get_signature(
16
+ path: str,
17
+ operation: Operation,
18
+ user: Optional[str],
19
+ envd_access_token: Optional[str],
20
+ expiration_in_seconds: Optional[int] = None,
21
+ ) -> Signature:
22
+ """
23
+ Generate a v1 signature for sandbox file URLs.
24
+ """
25
+ if not envd_access_token:
26
+ raise ValueError("Access token is not set and signature cannot be generated!")
27
+
28
+ expiration = (
29
+ int(time.time()) + expiration_in_seconds
30
+ if expiration_in_seconds is not None
31
+ else None
32
+ )
33
+
34
+ # if user is None, set it to empty string to handle default user
35
+ if user is None:
36
+ user = ""
37
+
38
+ raw = (
39
+ f"{path}:{operation}:{user}:{envd_access_token}"
40
+ if expiration is None
41
+ else f"{path}:{operation}:{user}:{envd_access_token}:{expiration}"
42
+ )
43
+
44
+ digest = hashlib.sha256(raw.encode("utf-8")).digest()
45
+ encoded = base64.b64encode(digest).rstrip(b"=").decode("ascii")
46
+
47
+ return {"signature": f"v1_{encoded}", "expiration": expiration}