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,88 @@
1
+ import asyncio
2
+ import os
3
+ import weakref
4
+ from typing import Dict, Optional
5
+
6
+ import httpx
7
+ from httpx import Limits
8
+ from httpx._types import ProxyTypes
9
+
10
+ from loopix.api import make_async_logging_event_hooks
11
+ from loopix.api.metadata import default_headers
12
+ from loopix.exceptions import AuthenticationException
13
+ from loopix.volume.client.client import AuthenticatedClient as AsyncVolumeApiClient
14
+ from loopix.volume.connection_config import VolumeConnectionConfig
15
+
16
+ limits = Limits(
17
+ max_keepalive_connections=int(os.getenv("LOOPIX_MAX_KEEPALIVE_CONNECTIONS", "20")),
18
+ max_connections=int(os.getenv("LOOPIX_MAX_CONNECTIONS", "2000")),
19
+ keepalive_expiry=int(os.getenv("LOOPIX_KEEPALIVE_EXPIRY", "300")),
20
+ )
21
+
22
+ TransportKey = Optional[ProxyTypes]
23
+
24
+
25
+ def get_api_client(config: VolumeConnectionConfig, **kwargs) -> AsyncVolumeApiClient:
26
+ if config.access_token is None:
27
+ raise AuthenticationException(
28
+ "Volume token is required for volume content operations. "
29
+ "Use `AsyncVolume.create`/`AsyncVolume.connect` to obtain it "
30
+ "or pass `token` in options.",
31
+ )
32
+
33
+ headers = {
34
+ **default_headers,
35
+ **(config.headers or {}),
36
+ }
37
+
38
+ request_timeout = config.request_timeout
39
+
40
+ return AsyncVolumeApiClient(
41
+ base_url=config.api_url,
42
+ token=config.access_token,
43
+ auth_header_name="Authorization",
44
+ prefix="Bearer",
45
+ headers=headers,
46
+ timeout=(
47
+ httpx.Timeout(request_timeout) if request_timeout is not None else None
48
+ ),
49
+ httpx_args={
50
+ "proxy": config.proxy,
51
+ "transport": get_transport(config),
52
+ "event_hooks": make_async_logging_event_hooks(config.logger),
53
+ },
54
+ **kwargs,
55
+ )
56
+
57
+
58
+ class AsyncTransportWithLogger(httpx.AsyncHTTPTransport):
59
+ # Keyed weakly by the event loop object itself, not id(loop) — CPython
60
+ # reuses object ids, so a new loop could otherwise inherit a transport
61
+ # bound to a previous, closed loop.
62
+ _instances: weakref.WeakKeyDictionary[
63
+ asyncio.AbstractEventLoop,
64
+ Dict[TransportKey, "AsyncTransportWithLogger"],
65
+ ] = weakref.WeakKeyDictionary()
66
+
67
+ @property
68
+ def pool(self):
69
+ return self._pool
70
+
71
+
72
+ def get_transport(config: VolumeConnectionConfig) -> AsyncTransportWithLogger:
73
+ loop = asyncio.get_running_loop()
74
+ loop_instances = AsyncTransportWithLogger._instances.get(loop)
75
+ if loop_instances is None:
76
+ loop_instances = {}
77
+ AsyncTransportWithLogger._instances[loop] = loop_instances
78
+
79
+ key: TransportKey = config.proxy
80
+ transport = loop_instances.get(key)
81
+ if transport is None:
82
+ transport = AsyncTransportWithLogger(
83
+ limits=limits,
84
+ proxy=config.proxy,
85
+ )
86
+ loop_instances[key] = transport
87
+
88
+ return transport
@@ -0,0 +1,80 @@
1
+ import os
2
+ import threading
3
+ from typing import Dict, Optional
4
+
5
+ import httpx
6
+ from httpx import Limits
7
+ from httpx._types import ProxyTypes
8
+
9
+ from loopix.api import make_logging_event_hooks
10
+ from loopix.api.metadata import default_headers
11
+ from loopix.exceptions import AuthenticationException
12
+ from loopix.volume.client.client import AuthenticatedClient as VolumeApiClient
13
+ from loopix.volume.connection_config import VolumeConnectionConfig
14
+
15
+ limits = Limits(
16
+ max_keepalive_connections=int(os.getenv("LOOPIX_MAX_KEEPALIVE_CONNECTIONS", "20")),
17
+ max_connections=int(os.getenv("LOOPIX_MAX_CONNECTIONS", "2000")),
18
+ keepalive_expiry=int(os.getenv("LOOPIX_KEEPALIVE_EXPIRY", "300")),
19
+ )
20
+
21
+ TransportKey = Optional[ProxyTypes]
22
+
23
+
24
+ def get_api_client(config: VolumeConnectionConfig, **kwargs) -> VolumeApiClient:
25
+ if config.access_token is None:
26
+ raise AuthenticationException(
27
+ "Volume token is required for volume content operations. "
28
+ "Use `Volume.create`/`Volume.connect` to obtain it "
29
+ "or pass `token` in options.",
30
+ )
31
+
32
+ headers = {
33
+ **default_headers,
34
+ **(config.headers or {}),
35
+ }
36
+
37
+ request_timeout = config.request_timeout
38
+
39
+ return VolumeApiClient(
40
+ base_url=config.api_url,
41
+ token=config.access_token,
42
+ auth_header_name="Authorization",
43
+ prefix="Bearer",
44
+ headers=headers,
45
+ timeout=(
46
+ httpx.Timeout(request_timeout) if request_timeout is not None else None
47
+ ),
48
+ httpx_args={
49
+ "proxy": config.proxy,
50
+ "transport": get_transport(config),
51
+ "event_hooks": make_logging_event_hooks(config.logger),
52
+ },
53
+ **kwargs,
54
+ )
55
+
56
+
57
+ class TransportWithLogger(httpx.HTTPTransport):
58
+ _thread_local = threading.local()
59
+
60
+ @property
61
+ def pool(self):
62
+ return self._pool
63
+
64
+
65
+ def get_transport(config: VolumeConnectionConfig) -> TransportWithLogger:
66
+ instances: Dict[TransportKey, TransportWithLogger] = getattr(
67
+ TransportWithLogger._thread_local, "instances", {}
68
+ )
69
+ key: TransportKey = config.proxy
70
+ cached = instances.get(key)
71
+ if cached is not None:
72
+ return cached
73
+
74
+ transport = TransportWithLogger(
75
+ limits=limits,
76
+ proxy=config.proxy,
77
+ )
78
+ instances[key] = transport
79
+ TransportWithLogger._thread_local.instances = instances
80
+ return transport
@@ -0,0 +1,145 @@
1
+ import logging
2
+ import os
3
+
4
+ from typing import Dict, Optional, TypedDict
5
+
6
+ from httpx._types import ProxyTypes
7
+ from typing_extensions import Unpack
8
+
9
+ from loopix.api.metadata import package_version
10
+
11
+ REQUEST_TIMEOUT: float = 60.0 # 60 seconds
12
+
13
+ # Timeout for volume file transfers, which stream large bodies and so must not
14
+ # inherit the short REQUEST_TIMEOUT. (Sandbox filesystem streaming instead
15
+ # bounds each chunk by the request timeout and leaves the total to the server.)
16
+ FILE_TIMEOUT: float = 3600.0 # 1 hour
17
+
18
+
19
+ class VolumeApiParams(TypedDict, total=False):
20
+ """
21
+ Parameters for requests made to the volume content API.
22
+ """
23
+
24
+ domain: Optional[str]
25
+ """Domain to use for the volume API, defaults to `LOOPIX_DOMAIN` or `vm.betmandu.net`."""
26
+
27
+ debug: Optional[bool]
28
+ """Whether to use debug mode, defaults to `LOOPIX_DEBUG` environment variable."""
29
+
30
+ request_timeout: Optional[float]
31
+ """Timeout for the request in **seconds**, defaults to 60 seconds."""
32
+
33
+ headers: Optional[Dict[str, str]]
34
+ """Additional headers to send with the request."""
35
+
36
+ token: Optional[str]
37
+ """Volume auth token used for `Authorization: Bearer <token>`."""
38
+
39
+ api_url: Optional[str]
40
+ """URL to use for the volume API, defaults to `LOOPIX_VOLUME_API_URL` or `https://<domain>/api`."""
41
+
42
+ proxy: Optional[ProxyTypes]
43
+ """Proxy to use for the request."""
44
+
45
+ logger: Optional[logging.Logger]
46
+ """Logger used for request and response logging. Accepts a standard library `logging.Logger`."""
47
+
48
+
49
+ class VolumeConnectionConfig:
50
+ """
51
+ Configuration for the volume content API.
52
+
53
+ Uses bearer token authentication and defaults to the volume content host.
54
+ """
55
+
56
+ @staticmethod
57
+ def _domain():
58
+ return os.getenv("LOOPIX_DOMAIN") or "vm.betmandu.net"
59
+
60
+ @staticmethod
61
+ def _debug():
62
+ return os.getenv("LOOPIX_DEBUG", "false").lower() == "true"
63
+
64
+ @staticmethod
65
+ def _volume_api_url():
66
+ return os.getenv("LOOPIX_VOLUME_API_URL")
67
+
68
+ @staticmethod
69
+ def _get_request_timeout(
70
+ default_timeout: Optional[float],
71
+ request_timeout: Optional[float],
72
+ ):
73
+ if request_timeout == 0:
74
+ return None
75
+ elif request_timeout is not None:
76
+ return request_timeout
77
+ else:
78
+ return default_timeout
79
+
80
+ def __init__(
81
+ self,
82
+ domain: Optional[str] = None,
83
+ debug: Optional[bool] = None,
84
+ token: Optional[str] = None,
85
+ api_url: Optional[str] = None,
86
+ request_timeout: Optional[float] = None,
87
+ headers: Optional[Dict[str, str]] = None,
88
+ proxy: Optional[ProxyTypes] = None,
89
+ logger: Optional[logging.Logger] = None,
90
+ ):
91
+ self.logger = logger
92
+ self.domain = domain or self._domain()
93
+ self.debug = debug if debug is not None else self._debug()
94
+
95
+ self.api_url = (
96
+ api_url
97
+ or self._volume_api_url()
98
+ or ("http://localhost:8080" if self.debug else f"https://{self.domain}/api")
99
+ )
100
+ self.access_token = token
101
+ self.token = self.access_token
102
+ self.proxy = proxy
103
+
104
+ self.headers = dict(headers) if headers else {}
105
+ self.headers["User-Agent"] = f"loopix-python-sdk/{package_version}"
106
+
107
+ self.request_timeout = self._get_request_timeout(
108
+ REQUEST_TIMEOUT, request_timeout
109
+ )
110
+
111
+ def get_request_timeout(self, request_timeout: Optional[float] = None):
112
+ return self._get_request_timeout(self.request_timeout, request_timeout)
113
+
114
+ def get_api_params(
115
+ self,
116
+ **opts: Unpack[VolumeApiParams],
117
+ ) -> dict:
118
+ """
119
+ Get request parameters for the volume content API.
120
+ """
121
+ domain = opts.get("domain")
122
+ debug = opts.get("debug")
123
+ headers = opts.get("headers")
124
+ request_timeout = opts.get("request_timeout")
125
+ token = opts.get("token")
126
+ api_url = opts.get("api_url")
127
+ proxy = opts.get("proxy")
128
+ logger = opts.get("logger")
129
+
130
+ req_headers = self.headers.copy()
131
+ if headers is not None:
132
+ req_headers.update(headers)
133
+
134
+ return dict(
135
+ VolumeApiParams(
136
+ domain=domain if domain is not None else self.domain,
137
+ debug=debug if debug is not None else self.debug,
138
+ token=token if token is not None else self.token,
139
+ api_url=api_url if api_url is not None else self.api_url,
140
+ request_timeout=self.get_request_timeout(request_timeout),
141
+ headers=req_headers,
142
+ proxy=proxy if proxy is not None else self.proxy,
143
+ logger=logger if logger is not None else self.logger,
144
+ )
145
+ )
loopix/volume/types.py ADDED
@@ -0,0 +1,62 @@
1
+ import datetime
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+ from loopix.volume.client.models.volume_entry_stat_type import VolumeEntryStatType
6
+
7
+ # Type alias for file type enum
8
+ VolumeFileType = VolumeEntryStatType
9
+
10
+
11
+ @dataclass
12
+ class VolumeInfo:
13
+ """Information about a volume."""
14
+
15
+ volume_id: str
16
+ """Volume ID."""
17
+ name: str
18
+ """Volume name."""
19
+
20
+
21
+ @dataclass
22
+ class VolumeAndToken(VolumeInfo):
23
+ """Information about a volume and its auth token."""
24
+
25
+ token: str
26
+ """Volume auth token."""
27
+
28
+
29
+ @dataclass
30
+ class VolumeEntryStat:
31
+ """Volume entry stat information."""
32
+
33
+ name: str
34
+ """Name of the filesystem object."""
35
+ type: VolumeFileType
36
+ """Type of the filesystem object."""
37
+ path: str
38
+ """Path to the filesystem object."""
39
+ size: int
40
+ """Size of the filesystem object."""
41
+ mode: int
42
+ """Mode of the filesystem object."""
43
+ uid: int
44
+ """User ID of the filesystem object."""
45
+ gid: int
46
+ """Group ID of the filesystem object."""
47
+ atime: datetime.datetime
48
+ """Access time."""
49
+ mtime: datetime.datetime
50
+ """Modification time."""
51
+ ctime: datetime.datetime
52
+ """Creation time."""
53
+ target: Optional[str] = None
54
+ """Target path for symlinks."""
55
+
56
+
57
+ __all__ = [
58
+ "VolumeInfo",
59
+ "VolumeAndToken",
60
+ "VolumeEntryStat",
61
+ "VolumeFileType",
62
+ ]
loopix/volume/utils.py ADDED
@@ -0,0 +1,52 @@
1
+ import datetime
2
+ from typing import Optional
3
+
4
+ from loopix.volume.client.models import VolumeEntryStat as VolumeEntryStatApi
5
+ from loopix.volume.client.types import UNSET
6
+ from loopix.volume.types import VolumeEntryStat
7
+
8
+
9
+ def _ensure_utc(dt: datetime.datetime) -> datetime.datetime:
10
+ """Mark a timezone-naive datetime as UTC (API timestamps are UTC)."""
11
+ if dt.tzinfo is None:
12
+ return dt.replace(tzinfo=datetime.timezone.utc)
13
+ return dt
14
+
15
+
16
+ def convert_volume_entry_stat(api_stat: VolumeEntryStatApi) -> VolumeEntryStat:
17
+ """Convert API VolumeEntryStat to SDK VolumeEntryStat."""
18
+ target: Optional[str] = None
19
+ if api_stat.target is not UNSET and api_stat.target is not None:
20
+ target = str(api_stat.target)
21
+
22
+ return VolumeEntryStat(
23
+ name=api_stat.name,
24
+ type=api_stat.type_,
25
+ path=api_stat.path,
26
+ size=api_stat.size,
27
+ mode=api_stat.mode,
28
+ uid=api_stat.uid,
29
+ gid=api_stat.gid,
30
+ atime=_ensure_utc(api_stat.atime),
31
+ mtime=_ensure_utc(api_stat.mtime),
32
+ ctime=_ensure_utc(api_stat.ctime),
33
+ target=target,
34
+ )
35
+
36
+
37
+ class DualMethod:
38
+ """Descriptor enabling the same name for a static (class-level) and instance method.
39
+
40
+ When accessed on the class (e.g. ``Volume.get_info``), the static function
41
+ is returned. When accessed on an instance (e.g. ``vol.get_info``), the
42
+ instance method is returned as a bound method.
43
+ """
44
+
45
+ def __init__(self, static_fn, instance_fn):
46
+ self._static_fn = static_fn
47
+ self._instance_fn = instance_fn
48
+
49
+ def __get__(self, obj, objtype=None):
50
+ if obj is None:
51
+ return self._static_fn
52
+ return self._instance_fn.__get__(obj, objtype)