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/__init__.py ADDED
@@ -0,0 +1,260 @@
1
+ """
2
+ Secure sandboxed cloud environments made for AI agents and AI apps.
3
+
4
+ Check docs [here](https://vm.betmandu.net/docs).
5
+
6
+ Loopix Sandbox is a secure cloud sandbox environment made for AI agents and AI
7
+ apps.
8
+ Sandboxes allow AI agents and apps to have long running cloud secure environments.
9
+ In these environments, large language models can use the same tools as humans do.
10
+
11
+ Loopix Python SDK supports both sync and async API:
12
+
13
+ ```py
14
+ from loopix import Sandbox
15
+
16
+ # Create sandbox
17
+ sandbox = Sandbox.create()
18
+ ```
19
+
20
+ ```py
21
+ from loopix import AsyncSandbox
22
+
23
+ # Create sandbox
24
+ sandbox = await AsyncSandbox.create()
25
+ ```
26
+ """
27
+
28
+ from .api import (
29
+ ApiClient,
30
+ client,
31
+ )
32
+ from .connection_config import (
33
+ ApiParams,
34
+ ConnectionConfig,
35
+ ProxyTypes,
36
+ Username,
37
+ )
38
+ from .volume.connection_config import VolumeApiParams, VolumeConnectionConfig
39
+ from .exceptions import (
40
+ AuthenticationException,
41
+ FileNotFoundException,
42
+ GitAuthException,
43
+ GitUpstreamException,
44
+ BuildException,
45
+ FileUploadException,
46
+ InvalidArgumentException,
47
+ NotEnoughSpaceException,
48
+ NotFoundException,
49
+ RateLimitException,
50
+ SandboxException,
51
+ SandboxNotFoundException,
52
+ TemplateException,
53
+ TimeoutException,
54
+ VolumeException,
55
+ )
56
+ from .sandbox.commands.command_handle import (
57
+ CommandExitException,
58
+ CommandResult,
59
+ PtyOutput,
60
+ PtySize,
61
+ Stderr,
62
+ Stdout,
63
+ )
64
+ from .sandbox.commands.main import ProcessInfo
65
+ from .sandbox.filesystem.filesystem import EntryInfo, FileType, WriteInfo
66
+ from .sandbox.filesystem.watch_handle import (
67
+ FilesystemEvent,
68
+ FilesystemEventType,
69
+ )
70
+ from .sandbox._git import GitBranches, GitFileStatus, GitResetMode, GitStatus
71
+ from .sandbox_sync.git import Git
72
+ from .sandbox.network import ALL_TRAFFIC
73
+ from .sandbox.signature import get_signature
74
+ from .sandbox.sandbox_api import (
75
+ GitHubMcpServer,
76
+ GitHubMcpServerConfig,
77
+ McpServer,
78
+ SandboxInfo,
79
+ SandboxInfoLifecycle,
80
+ SandboxMetrics,
81
+ SandboxLifecycle,
82
+ SandboxOnTimeout,
83
+ SandboxNetworkInfo,
84
+ SandboxNetworkOpts,
85
+ SandboxNetworkRule,
86
+ SandboxNetworkRuleInfo,
87
+ SandboxNetworkRules,
88
+ SandboxNetworkSelector,
89
+ SandboxNetworkSelectorContext,
90
+ SandboxNetworkTransform,
91
+ SandboxNetworkUpdate,
92
+ SandboxQuery,
93
+ SandboxState,
94
+ SnapshotInfo,
95
+ )
96
+ from .sandbox_async.commands.command_handle import AsyncCommandHandle
97
+ from .sandbox_async.filesystem.watch_handle import AsyncWatchHandle
98
+ from .sandbox_async.main import AsyncSandbox
99
+ from .sandbox_async.paginator import AsyncSandboxPaginator, AsyncSnapshotPaginator
100
+ from .sandbox_async.utils import OutputHandler
101
+ from .sandbox_sync.commands.command_handle import CommandHandle
102
+ from .sandbox_sync.filesystem.watch_handle import WatchHandle
103
+ from .sandbox_sync.main import Sandbox
104
+ from .sandbox_sync.paginator import SandboxPaginator, SnapshotPaginator
105
+ from .template.logger import (
106
+ LogEntry,
107
+ LogEntryEnd,
108
+ LogEntryLevel,
109
+ LogEntryStart,
110
+ default_build_logger,
111
+ )
112
+ from .template.main import TemplateBase, TemplateClass
113
+ from .template.readycmd import (
114
+ ReadyCmd,
115
+ wait_for_file,
116
+ wait_for_port,
117
+ wait_for_process,
118
+ wait_for_timeout,
119
+ wait_for_url,
120
+ )
121
+ from .template.types import (
122
+ BuildInfo,
123
+ BuildStatusReason,
124
+ CopyItem,
125
+ TemplateBuildStatus,
126
+ TemplateBuildStatusResponse,
127
+ TemplateTag,
128
+ TemplateTagInfo,
129
+ )
130
+ from .template_async.main import AsyncTemplate
131
+ from .template_sync.main import Template
132
+
133
+ from .volume.volume_sync import Volume
134
+ from .volume.volume_async import AsyncVolume
135
+ from .volume.types import (
136
+ VolumeInfo,
137
+ VolumeAndToken,
138
+ VolumeEntryStat,
139
+ VolumeFileType,
140
+ )
141
+
142
+ __all__ = [
143
+ # API
144
+ "ApiClient",
145
+ "client",
146
+ # Connection config
147
+ "ConnectionConfig",
148
+ "VolumeConnectionConfig",
149
+ "ProxyTypes",
150
+ "ApiParams",
151
+ "VolumeApiParams",
152
+ "Username",
153
+ # Exceptions
154
+ "SandboxException",
155
+ "TimeoutException",
156
+ "NotFoundException",
157
+ "FileNotFoundException",
158
+ "SandboxNotFoundException",
159
+ "AuthenticationException",
160
+ "GitAuthException",
161
+ "GitUpstreamException",
162
+ "InvalidArgumentException",
163
+ "NotEnoughSpaceException",
164
+ "TemplateException",
165
+ "BuildException",
166
+ "FileUploadException",
167
+ "RateLimitException",
168
+ "VolumeException",
169
+ # Sandbox API
170
+ "SandboxInfo",
171
+ "SandboxInfoLifecycle",
172
+ "SandboxMetrics",
173
+ "ProcessInfo",
174
+ "SandboxQuery",
175
+ "SandboxState",
176
+ "SandboxMetrics",
177
+ "GitStatus",
178
+ "GitBranches",
179
+ "GitFileStatus",
180
+ "GitResetMode",
181
+ # Command handle
182
+ "CommandResult",
183
+ "Stderr",
184
+ "Stdout",
185
+ "CommandExitException",
186
+ "PtyOutput",
187
+ "PtySize",
188
+ # Filesystem
189
+ "FilesystemEvent",
190
+ "FilesystemEventType",
191
+ "EntryInfo",
192
+ "WriteInfo",
193
+ "FileType",
194
+ # Network
195
+ "SandboxNetworkOpts",
196
+ "SandboxNetworkInfo",
197
+ "SandboxNetworkSelector",
198
+ "SandboxNetworkSelectorContext",
199
+ "SandboxNetworkRule",
200
+ "SandboxNetworkRuleInfo",
201
+ "SandboxNetworkRules",
202
+ "SandboxNetworkTransform",
203
+ "SandboxNetworkUpdate",
204
+ "SandboxLifecycle",
205
+ "SandboxOnTimeout",
206
+ "ALL_TRAFFIC",
207
+ # Snapshot
208
+ "SnapshotInfo",
209
+ "SnapshotPaginator",
210
+ "AsyncSnapshotPaginator",
211
+ # Signature
212
+ "get_signature",
213
+ # Sync sandbox
214
+ "Sandbox",
215
+ "SandboxPaginator",
216
+ "WatchHandle",
217
+ "CommandHandle",
218
+ # Async sandbox
219
+ "OutputHandler",
220
+ "AsyncSandboxPaginator",
221
+ "AsyncSandbox",
222
+ "AsyncWatchHandle",
223
+ "AsyncCommandHandle",
224
+ # Template
225
+ "Template",
226
+ "AsyncTemplate",
227
+ "TemplateBase",
228
+ "TemplateClass",
229
+ "CopyItem",
230
+ "BuildInfo",
231
+ "BuildStatusReason",
232
+ "TemplateBuildStatus",
233
+ "TemplateBuildStatusResponse",
234
+ "TemplateTag",
235
+ "TemplateTagInfo",
236
+ "ReadyCmd",
237
+ "wait_for_file",
238
+ "wait_for_url",
239
+ "wait_for_port",
240
+ "wait_for_process",
241
+ "wait_for_timeout",
242
+ "LogEntry",
243
+ "LogEntryStart",
244
+ "LogEntryEnd",
245
+ "LogEntryLevel",
246
+ "default_build_logger",
247
+ # MCP
248
+ "McpServer",
249
+ "GitHubMcpServer",
250
+ "GitHubMcpServerConfig",
251
+ # Git
252
+ "Git",
253
+ # Volume
254
+ "Volume",
255
+ "AsyncVolume",
256
+ "VolumeInfo",
257
+ "VolumeAndToken",
258
+ "VolumeEntryStat",
259
+ "VolumeFileType",
260
+ ]
loopix/api/__init__.py ADDED
@@ -0,0 +1,287 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import os
5
+ import re
6
+ import threading
7
+ import weakref
8
+ from dataclasses import dataclass
9
+ from types import TracebackType
10
+ from typing import Callable, Optional, Protocol, Union
11
+
12
+ import httpx
13
+ from httpx import AsyncBaseTransport, BaseTransport, Limits, Timeout
14
+
15
+ from loopix.api.client.client import AuthenticatedClient
16
+ from loopix.api.client.types import Response
17
+ from loopix.api.metadata import default_headers
18
+ from loopix.connection_config import ConnectionConfig
19
+ from loopix.exceptions import (
20
+ AuthenticationException,
21
+ RateLimitException,
22
+ SandboxException,
23
+ )
24
+
25
+
26
+ def make_logging_event_hooks(log: Optional[logging.Logger]) -> dict:
27
+ """Build synchronous httpx ``event_hooks`` that log requests and responses
28
+ to the given ``logging.Logger``. Requests log at ``INFO``, successful
29
+ responses at ``INFO`` and responses with status >= 400 at ``ERROR``.
30
+
31
+ Returns no hooks when ``log`` is ``None`` so that nothing is logged unless a
32
+ logger was explicitly supplied."""
33
+ if log is None:
34
+ return {}
35
+
36
+ def on_request(request) -> None:
37
+ log.info(f"Request {request.method} {request.url}")
38
+
39
+ def on_response(response: Response) -> None:
40
+ if response.status_code >= 400:
41
+ log.error(f"Response {response.status_code}")
42
+ else:
43
+ log.info(f"Response {response.status_code}")
44
+
45
+ return {"request": [on_request], "response": [on_response]}
46
+
47
+
48
+ def make_async_logging_event_hooks(log: Optional[logging.Logger]) -> dict:
49
+ """Asynchronous counterpart of :func:`make_logging_event_hooks`."""
50
+ if log is None:
51
+ return {}
52
+
53
+ async def on_request(request) -> None:
54
+ log.info(f"Request {request.method} {request.url}")
55
+
56
+ async def on_response(response: Response) -> None:
57
+ if response.status_code >= 400:
58
+ log.error(f"Response {response.status_code}")
59
+ else:
60
+ log.info(f"Response {response.status_code}")
61
+
62
+ return {"request": [on_request], "response": [on_response]}
63
+
64
+
65
+ limits = Limits(
66
+ max_keepalive_connections=int(os.getenv("LOOPIX_MAX_KEEPALIVE_CONNECTIONS", "20")),
67
+ max_connections=int(os.getenv("LOOPIX_MAX_CONNECTIONS", "2000")),
68
+ keepalive_expiry=int(os.getenv("LOOPIX_KEEPALIVE_EXPIRY", "300")),
69
+ )
70
+
71
+ connection_retries = int(os.getenv("LOOPIX_CONNECTION_RETRIES", "3"))
72
+
73
+
74
+ @dataclass
75
+ class SandboxCreateResponse:
76
+ sandbox_id: str
77
+ sandbox_domain: Optional[str]
78
+ envd_version: str
79
+ envd_access_token: Optional[str]
80
+ traffic_access_token: Optional[str]
81
+
82
+
83
+ def handle_api_exception(
84
+ e: "SupportsApiErrorResponse",
85
+ default_exception_class: type[Exception] = SandboxException,
86
+ stack_trace: Optional[TracebackType] = None,
87
+ ):
88
+ try:
89
+ body = json.loads(e.content) if e.content else {}
90
+ except json.JSONDecodeError:
91
+ body = {}
92
+
93
+ if e.status_code == 401:
94
+ message = f"{e.status_code}: Unauthorized, please check your credentials."
95
+ if body.get("message"):
96
+ message += f" - {body['message']}"
97
+ return AuthenticationException(message)
98
+
99
+ if e.status_code == 429:
100
+ message = f"{e.status_code}: Rate limit exceeded, please try again later."
101
+ if body.get("message"):
102
+ message += f" - {body['message']}"
103
+ return RateLimitException(message)
104
+
105
+ if "message" in body:
106
+ return default_exception_class(
107
+ f"{e.status_code}: {body['message']}"
108
+ ).with_traceback(stack_trace)
109
+ return default_exception_class(f"{e.status_code}: {e.content}").with_traceback(
110
+ stack_trace
111
+ )
112
+
113
+
114
+ class SupportsApiErrorResponse(Protocol):
115
+ @property
116
+ def status_code(self) -> int: ...
117
+
118
+ @property
119
+ def content(self) -> Union[str, bytes]: ...
120
+
121
+
122
+ _API_KEY_PATTERN = re.compile(r"\Alpx_[0-9a-f]+\Z")
123
+ _API_KEY_EXAMPLE = "lpx_" + "0" * 40
124
+
125
+
126
+ def validate_api_key(api_key: str) -> None:
127
+ """Validate that an Loopix API key has the expected ``lpx_`` prefix
128
+ followed by hex characters. Raises ``AuthenticationException`` otherwise.
129
+ """
130
+ if not _API_KEY_PATTERN.match(api_key):
131
+ raise AuthenticationException(
132
+ 'Invalid API key format: expected "lpx_" followed by hex '
133
+ f'characters (e.g. "{_API_KEY_EXAMPLE}"). '
134
+ "Visit the API Keys tab at https://vm.betmandu.net/dashboard?tab=keys to get your API key."
135
+ )
136
+
137
+
138
+ class ApiClient(AuthenticatedClient):
139
+ """
140
+ The client for interacting with the Loopix API.
141
+ """
142
+
143
+ def __init__(
144
+ self,
145
+ config: ConnectionConfig,
146
+ transport: Optional[Union[BaseTransport, AsyncBaseTransport]] = None,
147
+ transport_factory: Optional[Callable[[], BaseTransport]] = None,
148
+ async_transport_factory: Optional[Callable[[], AsyncBaseTransport]] = None,
149
+ *args,
150
+ **kwargs,
151
+ ):
152
+ if transport is not None and (
153
+ transport_factory is not None or async_transport_factory is not None
154
+ ):
155
+ raise ValueError("Use either transport or transport_factory, not both")
156
+
157
+ self._transport_factory = transport_factory
158
+ self._async_transport_factory = async_transport_factory
159
+ self._thread_local = threading.local()
160
+ # Keyed weakly by the event loop object itself, not id(loop) —
161
+ # CPython reuses object ids, so a new loop could otherwise inherit
162
+ # a client bound to a previous, closed loop.
163
+ self._async_clients: weakref.WeakKeyDictionary[
164
+ asyncio.AbstractEventLoop, httpx.AsyncClient
165
+ ] = weakref.WeakKeyDictionary()
166
+ self._proxy = config.proxy
167
+
168
+ if config.api_key is None:
169
+ raise AuthenticationException(
170
+ "API key is required, please visit the API Keys tab at https://vm.betmandu.net/dashboard?tab=keys to get your API key. "
171
+ "You can either set the environment variable `LOOPIX_API_KEY` "
172
+ 'or you can pass it directly to the method like api_key="lpx_..."',
173
+ )
174
+
175
+ if config.api_key is not None and config.validate_api_key:
176
+ validate_api_key(config.api_key)
177
+
178
+ token = config.api_key
179
+ auth_header_name = "X-API-KEY"
180
+ prefix = ""
181
+
182
+ self._logger = config.logger
183
+
184
+ headers = {
185
+ **default_headers,
186
+ # Deprecated: send the access token alongside the API key when one
187
+ # is available, mirroring the JS SDK. Prefer `api_headers` instead.
188
+ # Spread before `config.headers` so a custom `Authorization` in
189
+ # `api_headers` wins over the deprecated access token, matching JS.
190
+ **(
191
+ {"Authorization": f"Bearer {config.access_token}"}
192
+ if config.access_token is not None
193
+ else {}
194
+ ),
195
+ **(config.headers or {}),
196
+ }
197
+
198
+ # Prevent passing these parameters twice
199
+ more_headers: Optional[dict] = kwargs.pop("headers", None)
200
+ if more_headers:
201
+ headers.update(more_headers)
202
+ kwargs.pop("token", None)
203
+ kwargs.pop("auth_header_name", None)
204
+ kwargs.pop("prefix", None)
205
+
206
+ httpx_args = {
207
+ "event_hooks": self._logging_event_hooks(),
208
+ }
209
+ if transport is not None:
210
+ httpx_args["transport"] = transport
211
+ if (
212
+ transport is None
213
+ and transport_factory is None
214
+ and async_transport_factory is None
215
+ ):
216
+ httpx_args["proxy"] = config.proxy
217
+
218
+ # config.request_timeout is None when the timeout is explicitly
219
+ # disabled (request_timeout=0), which httpx.Timeout(None) preserves.
220
+ kwargs.setdefault("timeout", Timeout(config.request_timeout))
221
+
222
+ super().__init__(
223
+ base_url=config.api_url,
224
+ httpx_args=httpx_args,
225
+ headers=headers,
226
+ token=token or "",
227
+ auth_header_name=auth_header_name,
228
+ prefix=prefix,
229
+ *args,
230
+ **kwargs,
231
+ )
232
+
233
+ def _logging_event_hooks(self) -> dict:
234
+ return make_logging_event_hooks(self._logger)
235
+
236
+ def _headers_with_auth(self) -> dict:
237
+ return {
238
+ **self._headers,
239
+ self.auth_header_name: (
240
+ f"{self.prefix} {self.token}" if self.prefix else self.token
241
+ ),
242
+ }
243
+
244
+ def get_httpx_client(self) -> httpx.Client:
245
+ if self._client is not None or self._transport_factory is None:
246
+ return super().get_httpx_client()
247
+
248
+ client = getattr(self._thread_local, "client", None)
249
+ if client is None:
250
+ client = httpx.Client(
251
+ base_url=self._base_url,
252
+ cookies=self._cookies,
253
+ headers=self._headers_with_auth(),
254
+ timeout=self._timeout,
255
+ verify=self._verify_ssl,
256
+ follow_redirects=self._follow_redirects,
257
+ event_hooks=self._httpx_args.get("event_hooks"),
258
+ transport=self._transport_factory(),
259
+ )
260
+ self._thread_local.client = client
261
+ return client
262
+
263
+ def get_async_httpx_client(self) -> httpx.AsyncClient:
264
+ if self._async_client is not None or self._async_transport_factory is None:
265
+ return super().get_async_httpx_client()
266
+
267
+ loop = asyncio.get_running_loop()
268
+ client = self._async_clients.get(loop)
269
+ if client is None:
270
+ client = httpx.AsyncClient(
271
+ base_url=self._base_url,
272
+ cookies=self._cookies,
273
+ headers=self._headers_with_auth(),
274
+ timeout=self._timeout,
275
+ verify=self._verify_ssl,
276
+ follow_redirects=self._follow_redirects,
277
+ event_hooks=self._httpx_args.get("event_hooks"),
278
+ transport=self._async_transport_factory(),
279
+ )
280
+ self._async_clients[loop] = client
281
+ return client
282
+
283
+
284
+ # We need to override the logging hooks for the async usage
285
+ class AsyncApiClient(ApiClient):
286
+ def _logging_event_hooks(self) -> dict:
287
+ return make_async_logging_event_hooks(self._logger)
@@ -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"""