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,529 @@
1
+ from datetime import datetime
2
+ from typing import Callable, List, Optional, Union
3
+
4
+ from typing_extensions import Unpack
5
+
6
+ from loopix.api.client.client import AuthenticatedClient
7
+ from loopix.connection_config import ApiParams, ConnectionConfig
8
+
9
+ from loopix.api.client_sync import get_api_client
10
+ from loopix.template.consts import GZIP, RESOLVE_SYMLINKS
11
+ from loopix.template.logger import LogEntry, LogEntryEnd, LogEntryStart
12
+ from loopix.template.main import TemplateBase, TemplateClass
13
+ from loopix.template.types import BuildInfo, InstructionType, TemplateTag, TemplateTagInfo
14
+ from loopix.template_sync.build_api import (
15
+ assign_tags,
16
+ check_alias_exists,
17
+ get_template_tags,
18
+ remove_tags,
19
+ get_build_status,
20
+ get_file_upload_link,
21
+ request_build,
22
+ trigger_build,
23
+ upload_file,
24
+ wait_for_build_finish,
25
+ )
26
+ from loopix.template.utils import normalize_build_arguments, read_dockerignore
27
+
28
+
29
+ class Template(TemplateBase):
30
+ """
31
+ Synchronous template builder for Loopix sandboxes.
32
+ """
33
+
34
+ @staticmethod
35
+ def _build(
36
+ api_client: AuthenticatedClient,
37
+ template: TemplateClass,
38
+ name: str,
39
+ tags: Optional[List[str]] = None,
40
+ cpu_count: int = 2,
41
+ memory_mb: int = 1024,
42
+ skip_cache: bool = False,
43
+ on_build_logs: Optional[Callable[[LogEntry], None]] = None,
44
+ request_timeout: Optional[float] = None,
45
+ ) -> BuildInfo:
46
+ """
47
+ Internal implementation of the template build process
48
+
49
+ :param api_client: Authenticated API client
50
+ :param template: The template to build
51
+ :param name: Name for the template
52
+ :param tags: Optional tags for the template
53
+ :param cpu_count: Number of CPUs allocated to the sandbox
54
+ :param memory_mb: Amount of memory in MB allocated to the sandbox
55
+ :param skip_cache: If True, forces a complete rebuild ignoring cache
56
+ :param on_build_logs: Callback function to receive build logs during the build process
57
+ """
58
+ if skip_cache:
59
+ template._template._force = True
60
+
61
+ # Create template
62
+ if on_build_logs:
63
+ tags_msg = f" with tags: {', '.join(tags)}" if tags else ""
64
+ on_build_logs(
65
+ LogEntry(
66
+ timestamp=datetime.now(),
67
+ level="info",
68
+ message=f"Requesting build for template: {name}{tags_msg}",
69
+ )
70
+ )
71
+
72
+ response = request_build(
73
+ api_client,
74
+ name=name,
75
+ cpu_count=cpu_count,
76
+ memory_mb=memory_mb,
77
+ tags=tags,
78
+ )
79
+
80
+ template_id = response.template_id
81
+ build_id = response.build_id
82
+ response_tags = response.tags
83
+
84
+ if on_build_logs:
85
+ on_build_logs(
86
+ LogEntry(
87
+ timestamp=datetime.now(),
88
+ level="info",
89
+ message=f"Template created with ID: {template_id}, Build ID: {build_id}",
90
+ )
91
+ )
92
+
93
+ instructions_with_hashes = template._template._instructions_with_hashes()
94
+
95
+ # Upload files
96
+ for index, file_upload in enumerate(instructions_with_hashes):
97
+ if file_upload["type"] != InstructionType.COPY:
98
+ continue
99
+
100
+ args = file_upload.get("args", [])
101
+ src = args[0] if len(args) > 0 else None
102
+ force_upload = file_upload.get("forceUpload")
103
+ files_hash = file_upload.get("filesHash", None)
104
+ resolve_symlinks = file_upload.get("resolveSymlinks")
105
+ if resolve_symlinks is None:
106
+ resolve_symlinks = RESOLVE_SYMLINKS
107
+ gzip = file_upload.get("gzip")
108
+ if gzip is None:
109
+ gzip = GZIP
110
+
111
+ if src is None or files_hash is None:
112
+ raise ValueError("Source path and files hash are required")
113
+
114
+ stack_trace = None
115
+ if index + 1 < len(template._template._stack_traces):
116
+ stack_trace = template._template._stack_traces[index + 1]
117
+
118
+ file_info = get_file_upload_link(
119
+ api_client, template_id, files_hash, stack_trace
120
+ )
121
+
122
+ if (force_upload and file_info.url) or (
123
+ file_info.present is False and file_info.url
124
+ ):
125
+ upload_file(
126
+ api_client,
127
+ src,
128
+ template._template._file_context_path,
129
+ file_info.url,
130
+ [
131
+ *template._template._file_ignore_patterns,
132
+ *read_dockerignore(template._template._file_context_path),
133
+ ],
134
+ resolve_symlinks,
135
+ gzip,
136
+ stack_trace,
137
+ request_timeout=request_timeout,
138
+ )
139
+ if on_build_logs:
140
+ on_build_logs(
141
+ LogEntry(
142
+ timestamp=datetime.now(),
143
+ level="info",
144
+ message=f"Uploaded '{src}'",
145
+ )
146
+ )
147
+ else:
148
+ if on_build_logs:
149
+ on_build_logs(
150
+ LogEntry(
151
+ timestamp=datetime.now(),
152
+ level="info",
153
+ message=f"Skipping upload of '{src}', already cached",
154
+ )
155
+ )
156
+
157
+ if on_build_logs:
158
+ on_build_logs(
159
+ LogEntry(
160
+ timestamp=datetime.now(),
161
+ level="info",
162
+ message="All file uploads completed",
163
+ )
164
+ )
165
+
166
+ # Start build
167
+ if on_build_logs:
168
+ on_build_logs(
169
+ LogEntry(
170
+ timestamp=datetime.now(),
171
+ level="info",
172
+ message="Starting building...",
173
+ )
174
+ )
175
+
176
+ trigger_build(
177
+ api_client,
178
+ template_id,
179
+ build_id,
180
+ template._template._serialize(instructions_with_hashes),
181
+ )
182
+
183
+ return BuildInfo(
184
+ template_id=template_id,
185
+ build_id=build_id,
186
+ alias=name,
187
+ name=name,
188
+ tags=response_tags,
189
+ )
190
+
191
+ @staticmethod
192
+ def build(
193
+ template: TemplateClass,
194
+ name: Optional[str] = None,
195
+ *,
196
+ alias: Optional[str] = None,
197
+ tags: Optional[List[str]] = None,
198
+ cpu_count: int = 2,
199
+ memory_mb: int = 1024,
200
+ skip_cache: bool = False,
201
+ on_build_logs: Optional[Callable[[LogEntry], None]] = None,
202
+ **opts: Unpack[ApiParams],
203
+ ) -> BuildInfo:
204
+ """
205
+ Build and deploy a template to Loopix infrastructure.
206
+
207
+ :param template: The template to build
208
+ :param name: Template name in 'name' or 'name:tag' format
209
+ :param alias: (Deprecated) Alias name for the template. Use name instead.
210
+ :param tags: Optional additional tags to assign to the template
211
+ :param cpu_count: Number of CPUs allocated to the sandbox
212
+ :param memory_mb: Amount of memory in MB allocated to the sandbox
213
+ :param skip_cache: If True, forces a complete rebuild ignoring cache
214
+ :param on_build_logs: Callback function to receive build logs during the build process
215
+
216
+ Example
217
+ ```python
218
+ from loopix import Template
219
+
220
+ template = (
221
+ Template()
222
+ .from_python_image('3')
223
+ .copy('requirements.txt', '/home/user/')
224
+ .run_cmd('pip install -r /home/user/requirements.txt')
225
+ )
226
+
227
+ # Build with single tag
228
+ Template.build(template, 'my-python-env:v1.0')
229
+
230
+ # Build with multiple tags
231
+ Template.build(template, 'my-python-env', tags=['v1.1.0', 'stable'])
232
+ ```
233
+ """
234
+ name = normalize_build_arguments(name, alias)
235
+
236
+ try:
237
+ if on_build_logs:
238
+ on_build_logs(
239
+ LogEntryStart(
240
+ timestamp=datetime.now(),
241
+ message="Build started",
242
+ )
243
+ )
244
+
245
+ config = ConnectionConfig(**opts)
246
+ api_client = get_api_client(
247
+ config,
248
+ )
249
+
250
+ data = Template._build(
251
+ api_client,
252
+ template,
253
+ name,
254
+ tags=tags,
255
+ cpu_count=cpu_count,
256
+ memory_mb=memory_mb,
257
+ skip_cache=skip_cache,
258
+ on_build_logs=on_build_logs,
259
+ # Only honor an explicitly set request_timeout for uploads;
260
+ # otherwise upload_file applies its 1-hour default.
261
+ request_timeout=opts.get("request_timeout"),
262
+ )
263
+
264
+ if on_build_logs:
265
+ on_build_logs(
266
+ LogEntry(
267
+ timestamp=datetime.now(),
268
+ level="info",
269
+ message="Waiting for logs...",
270
+ )
271
+ )
272
+
273
+ wait_for_build_finish(
274
+ api_client,
275
+ data.template_id,
276
+ data.build_id,
277
+ on_build_logs,
278
+ logs_refresh_frequency=TemplateBase._logs_refresh_frequency,
279
+ stack_traces=template._template._stack_traces,
280
+ )
281
+
282
+ return data
283
+ finally:
284
+ if on_build_logs:
285
+ on_build_logs(
286
+ LogEntryEnd(
287
+ timestamp=datetime.now(),
288
+ message="Build finished",
289
+ )
290
+ )
291
+
292
+ @staticmethod
293
+ def build_in_background(
294
+ template: TemplateClass,
295
+ name: Optional[str] = None,
296
+ *,
297
+ alias: Optional[str] = None,
298
+ tags: Optional[List[str]] = None,
299
+ cpu_count: int = 2,
300
+ memory_mb: int = 1024,
301
+ skip_cache: bool = False,
302
+ on_build_logs: Optional[Callable[[LogEntry], None]] = None,
303
+ **opts: Unpack[ApiParams],
304
+ ) -> BuildInfo:
305
+ """
306
+ Build and deploy a template to Loopix infrastructure without waiting for completion.
307
+
308
+ :param template: The template to build
309
+ :param name: Template name in 'name' or 'name:tag' format
310
+ :param alias: (Deprecated) Alias name for the template. Use name instead.
311
+ :param tags: Optional additional tags to assign to the template
312
+ :param cpu_count: Number of CPUs allocated to the sandbox
313
+ :param memory_mb: Amount of memory in MB allocated to the sandbox
314
+ :param skip_cache: If True, forces a complete rebuild ignoring cache
315
+ :return: BuildInfo containing the template ID and build ID
316
+
317
+ Example
318
+ ```python
319
+ from loopix import Template
320
+
321
+ template = (
322
+ Template()
323
+ .from_python_image('3')
324
+ .run_cmd('echo "test"')
325
+ .set_start_cmd('echo "Hello"', 'sleep 1')
326
+ )
327
+
328
+ # Build with single tag
329
+ build_info = Template.build_in_background(template, 'my-python-env:v1.0')
330
+
331
+ # Build with multiple tags
332
+ build_info = Template.build_in_background(template, 'my-python-env', tags=['v1.1.0', 'stable'])
333
+ ```
334
+ """
335
+ name = normalize_build_arguments(name, alias)
336
+
337
+ config = ConnectionConfig(**opts)
338
+ api_client = get_api_client(
339
+ config,
340
+ )
341
+
342
+ return Template._build(
343
+ api_client,
344
+ template,
345
+ name,
346
+ tags=tags,
347
+ cpu_count=cpu_count,
348
+ memory_mb=memory_mb,
349
+ skip_cache=skip_cache,
350
+ on_build_logs=on_build_logs,
351
+ # Only honor an explicitly set request_timeout for uploads;
352
+ # otherwise upload_file applies its 1-hour default.
353
+ request_timeout=opts.get("request_timeout"),
354
+ )
355
+
356
+ @staticmethod
357
+ def get_build_status(
358
+ build_info: BuildInfo,
359
+ logs_offset: int = 0,
360
+ **opts: Unpack[ApiParams],
361
+ ):
362
+ """
363
+ Get the status of a build.
364
+
365
+ :param build_info: Build identifiers returned from build_in_background
366
+ :param logs_offset: Offset for fetching logs
367
+ :return: TemplateBuild containing the build status and logs
368
+
369
+ Example
370
+ ```python
371
+ from loopix import Template
372
+
373
+ build_info = Template.build_in_background(template, alias='my-template')
374
+ status = Template.get_build_status(build_info, logs_offset=0)
375
+ ```
376
+ """
377
+ config = ConnectionConfig(**opts)
378
+ api_client = get_api_client(
379
+ config,
380
+ )
381
+
382
+ return get_build_status(
383
+ api_client,
384
+ build_info.template_id,
385
+ build_info.build_id,
386
+ logs_offset,
387
+ )
388
+
389
+ @staticmethod
390
+ def exists(
391
+ name: str,
392
+ **opts: Unpack[ApiParams],
393
+ ) -> bool:
394
+ """
395
+ Check if a template with the given name exists.
396
+
397
+ :param name: Template name to check
398
+ :return: True if the name exists, False otherwise
399
+
400
+ Example
401
+ ```python
402
+ from loopix import Template
403
+
404
+ exists = Template.exists('my-python-env')
405
+ if exists:
406
+ print('Template exists!')
407
+ ```
408
+ """
409
+
410
+ return Template.alias_exists(name, **opts)
411
+
412
+ @staticmethod
413
+ def alias_exists(
414
+ alias: str,
415
+ **opts: Unpack[ApiParams],
416
+ ) -> bool:
417
+ """
418
+ Check if a template with the given alias exists.
419
+
420
+ Deprecated Use `exists` instead.
421
+
422
+ :param alias: Template alias to check
423
+ :return: True if the alias exists, False otherwise
424
+
425
+ Example
426
+ ```python
427
+ from loopix import Template
428
+
429
+ exists = Template.alias_exists('my-python-env')
430
+ if exists:
431
+ print('Template exists!')
432
+ ```
433
+ """
434
+ config = ConnectionConfig(**opts)
435
+ api_client = get_api_client(
436
+ config,
437
+ )
438
+
439
+ return check_alias_exists(api_client, alias)
440
+
441
+ @staticmethod
442
+ def assign_tags(
443
+ target_name: str,
444
+ tags: Union[str, List[str]],
445
+ **opts: Unpack[ApiParams],
446
+ ) -> TemplateTagInfo:
447
+ """
448
+ Assign tag(s) to an existing template build.
449
+
450
+ :param target_name: Template name in 'name:tag' format (the source build to tag from)
451
+ :param tags: Tag or tags to assign
452
+ :return: TemplateTagInfo with build_id and assigned tags
453
+
454
+ Example
455
+ ```python
456
+ from loopix import Template
457
+
458
+ # Assign a single tag
459
+ result = Template.assign_tags('my-template:v1.0', 'production')
460
+
461
+ # Assign multiple tags
462
+ result = Template.assign_tags('my-template:v1.0', ['production', 'stable'])
463
+ ```
464
+ """
465
+ config = ConnectionConfig(**opts)
466
+ api_client = get_api_client(
467
+ config,
468
+ )
469
+
470
+ normalized_tags = [tags] if isinstance(tags, str) else tags
471
+ return assign_tags(api_client, target_name, normalized_tags)
472
+
473
+ @staticmethod
474
+ def remove_tags(
475
+ name: str,
476
+ tags: Union[str, List[str]],
477
+ **opts: Unpack[ApiParams],
478
+ ) -> None:
479
+ """
480
+ Remove tag(s) from a template.
481
+
482
+ :param name: Template name
483
+ :param tags: Tag or tags to remove
484
+
485
+ Example
486
+ ```python
487
+ from loopix import Template
488
+
489
+ # Remove a single tag
490
+ Template.remove_tags('my-template', 'production')
491
+
492
+ # Remove multiple tags
493
+ Template.remove_tags('my-template', ['production', 'stable'])
494
+ ```
495
+ """
496
+ config = ConnectionConfig(**opts)
497
+ api_client = get_api_client(
498
+ config,
499
+ )
500
+
501
+ normalized_tags = [tags] if isinstance(tags, str) else tags
502
+ remove_tags(api_client, name, normalized_tags)
503
+
504
+ @staticmethod
505
+ def get_tags(
506
+ template_id: str,
507
+ **opts: Unpack[ApiParams],
508
+ ) -> List[TemplateTag]:
509
+ """
510
+ Get all tags for a template.
511
+
512
+ :param template_id: Template ID or name
513
+ :return: List of TemplateTag with tag name, build_id, and created_at
514
+
515
+ Example
516
+ ```python
517
+ from loopix import Template
518
+
519
+ tags = Template.get_tags('my-template')
520
+ for tag in tags:
521
+ print(f"Tag: {tag.tag}, Build: {tag.build_id}, Created: {tag.created_at}")
522
+ ```
523
+ """
524
+ config = ConnectionConfig(**opts)
525
+ api_client = get_api_client(
526
+ config,
527
+ )
528
+
529
+ return get_template_tags(api_client, template_id)
@@ -0,0 +1,8 @@
1
+ """A client library for accessing Loopix API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1 @@
1
+ """Contains endpoint functions for accessing the API"""
@@ -0,0 +1,174 @@
1
+ from http import HTTPStatus
2
+ from typing import Any, Optional, Union, cast
3
+
4
+ import httpx
5
+
6
+ from ... import errors
7
+ from ...client import AuthenticatedClient, Client
8
+ from ...models.error import Error
9
+ from ...types import UNSET, Response
10
+
11
+
12
+ def _get_kwargs(
13
+ volume_id: str,
14
+ *,
15
+ path: str,
16
+ ) -> dict[str, Any]:
17
+ params: dict[str, Any] = {}
18
+
19
+ params["path"] = path
20
+
21
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
22
+
23
+ _kwargs: dict[str, Any] = {
24
+ "method": "delete",
25
+ "url": f"/volumecontent/{volume_id}/path",
26
+ "params": params,
27
+ }
28
+
29
+ return _kwargs
30
+
31
+
32
+ def _parse_response(
33
+ *, client: Union[AuthenticatedClient, Client], response: httpx.Response
34
+ ) -> Optional[Union[Any, Error]]:
35
+ if response.status_code == 204:
36
+ response_204 = cast(Any, None)
37
+ return response_204
38
+ if response.status_code == 404:
39
+ response_404 = Error.from_dict(response.json())
40
+
41
+ return response_404
42
+ if client.raise_on_unexpected_status:
43
+ raise errors.UnexpectedStatus(response.status_code, response.content)
44
+ else:
45
+ return None
46
+
47
+
48
+ def _build_response(
49
+ *, client: Union[AuthenticatedClient, Client], response: httpx.Response
50
+ ) -> Response[Union[Any, Error]]:
51
+ return Response(
52
+ status_code=HTTPStatus(response.status_code),
53
+ content=response.content,
54
+ headers=response.headers,
55
+ parsed=_parse_response(client=client, response=response),
56
+ )
57
+
58
+
59
+ def sync_detailed(
60
+ volume_id: str,
61
+ *,
62
+ client: Union[AuthenticatedClient, Client],
63
+ path: str,
64
+ ) -> Response[Union[Any, Error]]:
65
+ """Delete a path
66
+
67
+ Args:
68
+ volume_id (str):
69
+ path (str):
70
+
71
+ Raises:
72
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
73
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
74
+
75
+ Returns:
76
+ Response[Union[Any, Error]]
77
+ """
78
+
79
+ kwargs = _get_kwargs(
80
+ volume_id=volume_id,
81
+ path=path,
82
+ )
83
+
84
+ response = client.get_httpx_client().request(
85
+ **kwargs,
86
+ )
87
+
88
+ return _build_response(client=client, response=response)
89
+
90
+
91
+ def sync(
92
+ volume_id: str,
93
+ *,
94
+ client: Union[AuthenticatedClient, Client],
95
+ path: str,
96
+ ) -> Optional[Union[Any, Error]]:
97
+ """Delete a path
98
+
99
+ Args:
100
+ volume_id (str):
101
+ path (str):
102
+
103
+ Raises:
104
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
105
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
106
+
107
+ Returns:
108
+ Union[Any, Error]
109
+ """
110
+
111
+ return sync_detailed(
112
+ volume_id=volume_id,
113
+ client=client,
114
+ path=path,
115
+ ).parsed
116
+
117
+
118
+ async def asyncio_detailed(
119
+ volume_id: str,
120
+ *,
121
+ client: Union[AuthenticatedClient, Client],
122
+ path: str,
123
+ ) -> Response[Union[Any, Error]]:
124
+ """Delete a path
125
+
126
+ Args:
127
+ volume_id (str):
128
+ path (str):
129
+
130
+ Raises:
131
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
132
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
133
+
134
+ Returns:
135
+ Response[Union[Any, Error]]
136
+ """
137
+
138
+ kwargs = _get_kwargs(
139
+ volume_id=volume_id,
140
+ path=path,
141
+ )
142
+
143
+ response = await client.get_async_httpx_client().request(**kwargs)
144
+
145
+ return _build_response(client=client, response=response)
146
+
147
+
148
+ async def asyncio(
149
+ volume_id: str,
150
+ *,
151
+ client: Union[AuthenticatedClient, Client],
152
+ path: str,
153
+ ) -> Optional[Union[Any, Error]]:
154
+ """Delete a path
155
+
156
+ Args:
157
+ volume_id (str):
158
+ path (str):
159
+
160
+ Raises:
161
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
162
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
163
+
164
+ Returns:
165
+ Union[Any, Error]
166
+ """
167
+
168
+ return (
169
+ await asyncio_detailed(
170
+ volume_id=volume_id,
171
+ client=client,
172
+ path=path,
173
+ )
174
+ ).parsed