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
loopix/envd/rpc.py ADDED
@@ -0,0 +1,139 @@
1
+ import base64
2
+
3
+ import httpcore
4
+ from typing import Awaitable, Callable, Optional
5
+ from packaging.version import Version
6
+ from loopix_connect.client import Code, ConnectException
7
+
8
+ from loopix.exceptions import (
9
+ SandboxException,
10
+ InvalidArgumentException,
11
+ NotFoundException,
12
+ TimeoutException,
13
+ format_sandbox_timeout_exception,
14
+ AuthenticationException,
15
+ RateLimitException,
16
+ )
17
+ from loopix.connection_config import Username, default_username
18
+ from loopix.envd.versions import ENVD_DEFAULT_USER
19
+
20
+ _DEFAULT_RPC_ERROR_MAP: dict[Code, Callable[[str], Exception]] = {
21
+ Code.invalid_argument: InvalidArgumentException,
22
+ Code.unauthenticated: AuthenticationException,
23
+ Code.not_found: NotFoundException,
24
+ Code.unavailable: format_sandbox_timeout_exception,
25
+ Code.resource_exhausted: lambda message: RateLimitException(
26
+ f"{message}: Rate limit exceeded, please try again later."
27
+ ),
28
+ Code.canceled: lambda message: TimeoutException(
29
+ f"{message}: This error is likely due to exceeding 'request_timeout'. You can pass the request timeout value as an option when making the request."
30
+ ),
31
+ Code.deadline_exceeded: lambda message: TimeoutException(
32
+ f"{message}: This error is likely due to exceeding 'timeout' — the total time a long running request (like process or directory watch) can be active. It can be modified by passing 'timeout' when making the request. Use '0' to disable the timeout."
33
+ ),
34
+ }
35
+
36
+
37
+ def format_terminated_exception(
38
+ e: Exception,
39
+ sandbox_running: Optional[bool],
40
+ ) -> Exception:
41
+ """Handle an exception for a connection to the sandbox dropped mid-request: when a
42
+ sandbox health probe confirmed the sandbox is gone (``sandbox_running is False``),
43
+ return a ``TimeoutException``; otherwise return the original error unchanged."""
44
+ if sandbox_running is False:
45
+ return TimeoutException(
46
+ f"{e}: The sandbox was killed or reached its end of life while the request was in flight."
47
+ )
48
+ return e
49
+
50
+
51
+ def handle_rpc_exception(
52
+ e: Exception,
53
+ error_map: Optional[dict[Code, Callable[[str], Exception]]] = None,
54
+ sandbox_running: Optional[bool] = None,
55
+ ):
56
+ """Handle errors from envd RPC calls by mapping gRPC status codes to specific exception types.
57
+
58
+ :param e: The caught exception, expected to be a ``ConnectException`` or a transport-level ``httpcore`` error.
59
+ :param error_map: Optional map of gRPC codes to exception factories that override the defaults.
60
+ :param sandbox_running: Result of a sandbox health probe (``None`` when unknown), used to disambiguate a connection dropped mid-request.
61
+ :return: The corresponding exception. A connection dropped mid-request with the sandbox confirmed gone becomes a ``TimeoutException``; non-``ConnectException`` errors are otherwise returned as-is.
62
+ """
63
+ if isinstance(e, ConnectException):
64
+ if error_map and e.status in error_map:
65
+ return error_map[e.status](e.message)
66
+
67
+ if e.status in _DEFAULT_RPC_ERROR_MAP:
68
+ return _DEFAULT_RPC_ERROR_MAP[e.status](e.message)
69
+
70
+ return SandboxException(f"{e.status}: {e.message}")
71
+
72
+ # A remote protocol error (e.g. an HTTP/2 stream reset) means the connection to the
73
+ # sandbox was dropped mid-request — either the sandbox died or the network failed
74
+ if isinstance(e, httpcore.RemoteProtocolError):
75
+ return format_terminated_exception(e, sandbox_running)
76
+
77
+ # A transport-level timeout from httpcore means a configured timeout was exceeded
78
+ # before the server responded: `request_timeout` on a unary call's read phase, or
79
+ # `connect`/`pool`/`write` on a stream's setup/send phase. Streams have no read
80
+ # timeout — the command `timeout` is enforced server-side and surfaces as a
81
+ # `deadline_exceeded` ConnectException instead. Unlike the JS SDK, where the
82
+ # request timeout is an `AbortSignal` that connect normalizes into a `Code.canceled`
83
+ # ConnectError, httpcore raises this raw transport error outside the ConnectException
84
+ # path, so we map it here to a `TimeoutException` for a consistent timeout error.
85
+ if isinstance(e, httpcore.TimeoutException):
86
+ return TimeoutException(
87
+ f"{e}: This error is likely due to exceeding 'timeout' — the total time a long running request (like process or directory watch) can be active — or 'request_timeout'. You can modify these by passing 'timeout' or 'request_timeout' when making the request. Use '0' to disable the timeout."
88
+ )
89
+
90
+ return e
91
+
92
+
93
+ def handle_rpc_exception_with_health(
94
+ e: Exception,
95
+ check_health: Optional[Callable[[], Optional[bool]]] = None,
96
+ error_map: Optional[dict[Code, Callable[[str], Exception]]] = None,
97
+ ):
98
+ """Like :func:`handle_rpc_exception`, but when the connection to the sandbox was
99
+ dropped mid-request it probes the sandbox health to tell apart the sandbox being
100
+ killed from a transient network failure (e.g. a load balancer dropping the connection).
101
+ """
102
+ sandbox_running = None
103
+ if check_health is not None and isinstance(e, httpcore.RemoteProtocolError):
104
+ try:
105
+ sandbox_running = check_health()
106
+ except Exception:
107
+ sandbox_running = None
108
+ return handle_rpc_exception(e, error_map, sandbox_running)
109
+
110
+
111
+ async def ahandle_rpc_exception_with_health(
112
+ e: Exception,
113
+ check_health: Optional[Callable[[], Awaitable[Optional[bool]]]] = None,
114
+ error_map: Optional[dict[Code, Callable[[str], Exception]]] = None,
115
+ ):
116
+ """Async version of :func:`handle_rpc_exception_with_health`."""
117
+ sandbox_running = None
118
+ if check_health is not None and isinstance(e, httpcore.RemoteProtocolError):
119
+ try:
120
+ sandbox_running = await check_health()
121
+ except Exception:
122
+ sandbox_running = None
123
+ return handle_rpc_exception(e, error_map, sandbox_running)
124
+
125
+
126
+ def authentication_header(
127
+ envd_version: Version, user: Optional[Username] = None
128
+ ) -> dict[str, str]:
129
+ if user is None and envd_version < ENVD_DEFAULT_USER:
130
+ user = default_username
131
+
132
+ if not user:
133
+ return {}
134
+
135
+ value = f"{user}:"
136
+
137
+ encoded = base64.b64encode(value.encode("utf-8")).decode("utf-8")
138
+
139
+ return {"Authorization": f"Basic {encoded}"}
@@ -0,0 +1,11 @@
1
+ from packaging.version import Version
2
+
3
+ ENVD_VERSION_RECURSIVE_WATCH = Version("0.1.4")
4
+ ENVD_DEBUG_FALLBACK = Version("99.99.99")
5
+ ENVD_COMMANDS_STDIN = Version("0.3.0")
6
+ ENVD_DEFAULT_USER = Version("0.4.0")
7
+ ENVD_ENVD_CLOSE = Version("0.5.2")
8
+ ENVD_OCTET_STREAM_UPLOAD = Version("0.5.7")
9
+ ENVD_FILE_METADATA = Version("0.6.2")
10
+ ENVD_VERSION_FS_EVENT_ENTRY_INFO = Version("0.6.3")
11
+ ENVD_VERSION_WATCH_NETWORK_MOUNTS = Version("0.6.4")
loopix/exceptions.py ADDED
@@ -0,0 +1,133 @@
1
+ def format_sandbox_timeout_exception(message: str):
2
+ return TimeoutException(
3
+ f"{message}: This error is likely due to sandbox timeout. You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout."
4
+ )
5
+
6
+
7
+ def format_request_timeout_error() -> Exception:
8
+ return TimeoutException(
9
+ "Request timed out — the 'request_timeout' option can be used to increase this timeout",
10
+ )
11
+
12
+
13
+ class SandboxException(Exception):
14
+ """
15
+ Base class for all sandbox errors.
16
+
17
+ Raised when a general sandbox exception occurs.
18
+ """
19
+
20
+ pass
21
+
22
+
23
+ class TimeoutException(SandboxException):
24
+ """
25
+ Raised when a timeout occurs.
26
+
27
+ The `unavailable` exception type is caused by sandbox timeout.\n
28
+ The `canceled` exception type is caused by exceeding request timeout.\n
29
+ The `deadline_exceeded` exception type is caused by exceeding the timeout for process, watch, etc.\n
30
+ The `unknown` exception type is sometimes caused by the sandbox timeout when the request is not processed correctly.\n
31
+ """
32
+
33
+ pass
34
+
35
+
36
+ class InvalidArgumentException(SandboxException):
37
+ """
38
+ Raised when an invalid argument is provided.
39
+ """
40
+
41
+ pass
42
+
43
+
44
+ class NotEnoughSpaceException(SandboxException):
45
+ """
46
+ Raised when there is not enough disk space.
47
+ """
48
+
49
+ pass
50
+
51
+
52
+ class NotFoundException(SandboxException):
53
+ """
54
+ Raised when a resource is not found.
55
+
56
+ .. deprecated::
57
+ Use :class:`FileNotFoundException` or :class:`SandboxNotFoundException` instead.
58
+ This class will be removed in the next major version.
59
+ """
60
+
61
+ pass
62
+
63
+
64
+ class FileNotFoundException(NotFoundException):
65
+ """
66
+ Raised when a file or directory is not found inside a sandbox.
67
+ """
68
+
69
+ pass
70
+
71
+
72
+ class SandboxNotFoundException(NotFoundException):
73
+ """
74
+ Raised when a sandbox is not found (e.g. it doesn't exist or is no longer running).
75
+ """
76
+
77
+ pass
78
+
79
+
80
+ class AuthenticationException(Exception):
81
+ """
82
+ Raised when authentication fails.
83
+ """
84
+
85
+ pass
86
+
87
+
88
+ class GitAuthException(AuthenticationException):
89
+ """
90
+ Raised when git authentication fails.
91
+ """
92
+
93
+ pass
94
+
95
+
96
+ class GitUpstreamException(SandboxException):
97
+ """
98
+ Raised when git upstream tracking is missing.
99
+ """
100
+
101
+ pass
102
+
103
+
104
+ class TemplateException(SandboxException):
105
+ """
106
+ Exception raised when the template uses old envd version. It isn't compatible with the new SDK.
107
+ """
108
+
109
+
110
+ class RateLimitException(SandboxException):
111
+ """
112
+ Raised when the API rate limit is exceeded.
113
+ """
114
+
115
+
116
+ class BuildException(Exception):
117
+ """
118
+ Raised when the build fails.
119
+ """
120
+
121
+
122
+ class FileUploadException(BuildException):
123
+ """
124
+ Raised when the file upload fails.
125
+ """
126
+
127
+
128
+ class VolumeException(Exception):
129
+ """
130
+ Base class for all volume errors.
131
+
132
+ Raised when general volume errors occur.
133
+ """
loopix/io_utils.py ADDED
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import zlib
3
+ from typing import IO, AsyncIterable, AsyncIterator, Iterable, Iterator
4
+
5
+ IO_CHUNK_SIZE = 65_536
6
+
7
+
8
+ def iter_io_chunks(data: IO) -> Iterator[bytes]:
9
+ """Read a file-like object in chunks, encoding text chunks to UTF-8."""
10
+ while True:
11
+ chunk = data.read(IO_CHUNK_SIZE)
12
+ if not chunk:
13
+ break
14
+ yield chunk if isinstance(chunk, bytes) else chunk.encode("utf-8")
15
+
16
+
17
+ async def aiter_io_chunks(data: IO) -> AsyncIterator[bytes]:
18
+ """Read a file-like object in chunks, encoding text chunks to UTF-8.
19
+
20
+ `data.read` is a synchronous (potentially disk-blocking) call, so it runs in
21
+ a worker thread to avoid stalling the event loop during large uploads.
22
+ """
23
+ while True:
24
+ chunk = await asyncio.to_thread(data.read, IO_CHUNK_SIZE)
25
+ if not chunk:
26
+ break
27
+ yield chunk if isinstance(chunk, bytes) else chunk.encode("utf-8")
28
+
29
+
30
+ def _gzip_compressor():
31
+ # wbits > 16 makes zlib produce a gzip-formatted stream.
32
+ return zlib.compressobj(wbits=zlib.MAX_WBITS | 16)
33
+
34
+
35
+ def gzip_iter(chunks: Iterable[bytes]) -> Iterator[bytes]:
36
+ """Gzip-compress a byte stream chunk by chunk."""
37
+ compressor = _gzip_compressor()
38
+ for chunk in chunks:
39
+ compressed = compressor.compress(chunk)
40
+ if compressed:
41
+ yield compressed
42
+ yield compressor.flush()
43
+
44
+
45
+ async def agzip_iter(chunks: AsyncIterable[bytes]) -> AsyncIterator[bytes]:
46
+ """Gzip-compress a byte stream chunk by chunk.
47
+
48
+ Compression is CPU-bound, so it runs in a worker thread to avoid stalling
49
+ the event loop during large uploads (zlib releases the GIL while
50
+ compressing, so the offload genuinely overlaps with the loop).
51
+ """
52
+ compressor = _gzip_compressor()
53
+ async for chunk in chunks:
54
+ compressed = await asyncio.to_thread(compressor.compress, chunk)
55
+ if compressed:
56
+ yield compressed
57
+ yield await asyncio.to_thread(compressor.flush)
loopix/paginator.py ADDED
@@ -0,0 +1,52 @@
1
+ from typing import Generic, Mapping, Optional, TypeVar
2
+
3
+ from typing_extensions import Unpack
4
+
5
+ from loopix.connection_config import ApiParams
6
+
7
+ T = TypeVar("T")
8
+ OptsT = TypeVar("OptsT", bound=ApiParams)
9
+
10
+
11
+ class PaginatorBase(Generic[T, OptsT]):
12
+ """
13
+ Shared pagination state for cursor-based list endpoints.
14
+
15
+ Owns the `has_next` / `next_token` state and the reading of the
16
+ `x-next-token` response header (via `_update_pagination`). Each concrete
17
+ paginator implements `next_items` to do the actual fetching for its
18
+ endpoint, so any model can expose pagination by subclassing this without
19
+ reimplementing the bookkeeping.
20
+
21
+ `T` is the item type returned by `next_items`; `OptsT` is the connection
22
+ options type accepted by the paginator (an `ApiParams`-compatible TypedDict).
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ limit: Optional[int] = None,
28
+ next_token: Optional[str] = None,
29
+ **opts: Unpack[OptsT],
30
+ ):
31
+ self._opts: OptsT = opts
32
+ self.limit = limit
33
+ self._has_next = True
34
+ self._next_token = next_token
35
+
36
+ @property
37
+ def has_next(self) -> bool:
38
+ """
39
+ Returns True if there are more items to fetch.
40
+ """
41
+ return self._has_next
42
+
43
+ @property
44
+ def next_token(self) -> Optional[str]:
45
+ """
46
+ Returns the next token to use for pagination.
47
+ """
48
+ return self._next_token
49
+
50
+ def _update_pagination(self, headers: Mapping[str, str]) -> None:
51
+ self._next_token = headers.get("x-next-token")
52
+ self._has_next = bool(self._next_token)
loopix/py.typed ADDED
File without changes
@@ -0,0 +1,85 @@
1
+ from loopix.sandbox._git.args import (
2
+ build_add_args,
3
+ build_branches_args,
4
+ build_checkout_branch_args,
5
+ build_clone_plan,
6
+ build_commit_args,
7
+ build_credential_approve_command,
8
+ build_create_branch_args,
9
+ build_delete_branch_args,
10
+ build_git_command,
11
+ build_has_upstream_args,
12
+ build_pull_args,
13
+ build_push_args,
14
+ build_remote_add_args,
15
+ build_remote_add_shell_command,
16
+ build_remote_get_command,
17
+ build_remote_get_url_args,
18
+ build_remote_set_url_args,
19
+ build_reset_args,
20
+ build_restore_args,
21
+ build_status_args,
22
+ shell_escape,
23
+ )
24
+ from loopix.sandbox._git.auth import (
25
+ build_auth_error_message,
26
+ build_upstream_error_message,
27
+ is_auth_failure,
28
+ is_missing_upstream,
29
+ strip_credentials,
30
+ with_credentials,
31
+ )
32
+ from loopix.sandbox._git.config import resolve_config_scope
33
+ from loopix.sandbox._git.parse import (
34
+ derive_repo_dir_from_url,
35
+ parse_git_branches,
36
+ parse_git_status,
37
+ parse_remote_url,
38
+ )
39
+ from loopix.sandbox._git.types import (
40
+ ClonePlan,
41
+ GitBranches,
42
+ GitFileStatus,
43
+ GitResetMode,
44
+ GitStatus,
45
+ )
46
+
47
+ __all__ = [
48
+ "build_add_args",
49
+ "build_auth_error_message",
50
+ "build_branches_args",
51
+ "build_checkout_branch_args",
52
+ "build_clone_plan",
53
+ "build_commit_args",
54
+ "build_credential_approve_command",
55
+ "build_create_branch_args",
56
+ "build_delete_branch_args",
57
+ "build_git_command",
58
+ "build_has_upstream_args",
59
+ "build_pull_args",
60
+ "build_push_args",
61
+ "build_remote_add_args",
62
+ "build_remote_add_shell_command",
63
+ "build_remote_get_command",
64
+ "build_remote_get_url_args",
65
+ "build_remote_set_url_args",
66
+ "build_reset_args",
67
+ "build_restore_args",
68
+ "build_status_args",
69
+ "build_upstream_error_message",
70
+ "derive_repo_dir_from_url",
71
+ "is_auth_failure",
72
+ "is_missing_upstream",
73
+ "parse_git_branches",
74
+ "parse_git_status",
75
+ "parse_remote_url",
76
+ "resolve_config_scope",
77
+ "shell_escape",
78
+ "strip_credentials",
79
+ "with_credentials",
80
+ "ClonePlan",
81
+ "GitBranches",
82
+ "GitFileStatus",
83
+ "GitResetMode",
84
+ "GitStatus",
85
+ ]