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.
- loopix/__init__.py +260 -0
- loopix/api/__init__.py +287 -0
- loopix/api/client/__init__.py +8 -0
- loopix/api/client/api/__init__.py +1 -0
- loopix/api/client/api/sandboxes/__init__.py +1 -0
- loopix/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
- loopix/api/client/api/sandboxes/get_sandboxes.py +176 -0
- loopix/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
- loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
- loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
- loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
- loopix/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
- loopix/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py +254 -0
- loopix/api/client/api/sandboxes/post_sandboxes.py +172 -0
- loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +187 -0
- loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
- loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
- loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py +195 -0
- loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
- loopix/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py +199 -0
- loopix/api/client/api/snapshots/__init__.py +1 -0
- loopix/api/client/api/snapshots/get_snapshots.py +202 -0
- loopix/api/client/api/tags/__init__.py +1 -0
- loopix/api/client/api/tags/delete_templates_tags.py +174 -0
- loopix/api/client/api/tags/get_templates_template_id_tags.py +172 -0
- loopix/api/client/api/tags/post_templates_tags.py +176 -0
- loopix/api/client/api/templates/__init__.py +1 -0
- loopix/api/client/api/templates/delete_templates_template_id.py +157 -0
- loopix/api/client/api/templates/get_templates.py +172 -0
- loopix/api/client/api/templates/get_templates_aliases_alias.py +167 -0
- loopix/api/client/api/templates/get_templates_template_id.py +195 -0
- loopix/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py +272 -0
- loopix/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +232 -0
- loopix/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
- loopix/api/client/api/templates/patch_templates_template_id.py +183 -0
- loopix/api/client/api/templates/patch_v_2_templates_template_id.py +185 -0
- loopix/api/client/api/templates/post_templates.py +172 -0
- loopix/api/client/api/templates/post_templates_template_id.py +181 -0
- loopix/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
- loopix/api/client/api/templates/post_v2_templates.py +172 -0
- loopix/api/client/api/templates/post_v3_templates.py +176 -0
- loopix/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
- loopix/api/client/api/volumes/__init__.py +1 -0
- loopix/api/client/api/volumes/delete_volumes_volume_id.py +161 -0
- loopix/api/client/api/volumes/get_volumes.py +140 -0
- loopix/api/client/api/volumes/get_volumes_volume_id.py +163 -0
- loopix/api/client/api/volumes/post_volumes.py +172 -0
- loopix/api/client/client.py +286 -0
- loopix/api/client/errors.py +16 -0
- loopix/api/client/models/__init__.py +185 -0
- loopix/api/client/models/admin_build_cancel_result.py +67 -0
- loopix/api/client/models/admin_sandbox_kill_result.py +67 -0
- loopix/api/client/models/assign_template_tags_request.py +67 -0
- loopix/api/client/models/assigned_template_tags.py +68 -0
- loopix/api/client/models/aws_registry.py +85 -0
- loopix/api/client/models/aws_registry_type.py +8 -0
- loopix/api/client/models/build_log_entry.py +89 -0
- loopix/api/client/models/build_status_reason.py +95 -0
- loopix/api/client/models/connect_sandbox.py +59 -0
- loopix/api/client/models/created_access_token.py +100 -0
- loopix/api/client/models/created_team_api_key.py +166 -0
- loopix/api/client/models/delete_template_tags_request.py +67 -0
- loopix/api/client/models/disk_metrics.py +91 -0
- loopix/api/client/models/error.py +67 -0
- loopix/api/client/models/gcp_registry.py +69 -0
- loopix/api/client/models/gcp_registry_type.py +8 -0
- loopix/api/client/models/general_registry.py +77 -0
- loopix/api/client/models/general_registry_type.py +8 -0
- loopix/api/client/models/identifier_masking_details.py +83 -0
- loopix/api/client/models/listed_sandbox.py +179 -0
- loopix/api/client/models/log_level.py +11 -0
- loopix/api/client/models/logs_direction.py +9 -0
- loopix/api/client/models/logs_source.py +9 -0
- loopix/api/client/models/machine_info.py +83 -0
- loopix/api/client/models/max_team_metric.py +78 -0
- loopix/api/client/models/mcp_type_0.py +44 -0
- loopix/api/client/models/new_access_token.py +59 -0
- loopix/api/client/models/new_sandbox.py +224 -0
- loopix/api/client/models/new_team_api_key.py +59 -0
- loopix/api/client/models/new_volume.py +59 -0
- loopix/api/client/models/node.py +160 -0
- loopix/api/client/models/node_detail.py +160 -0
- loopix/api/client/models/node_metrics.py +122 -0
- loopix/api/client/models/node_status.py +12 -0
- loopix/api/client/models/node_status_change.py +82 -0
- loopix/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
- loopix/api/client/models/post_sandboxes_sandbox_id_snapshots_body.py +60 -0
- loopix/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
- loopix/api/client/models/resumed_sandbox.py +68 -0
- loopix/api/client/models/sandbox.py +145 -0
- loopix/api/client/models/sandbox_auto_resume_config.py +60 -0
- loopix/api/client/models/sandbox_detail.py +267 -0
- loopix/api/client/models/sandbox_lifecycle.py +70 -0
- loopix/api/client/models/sandbox_log.py +70 -0
- loopix/api/client/models/sandbox_log_entry.py +93 -0
- loopix/api/client/models/sandbox_log_entry_fields.py +44 -0
- loopix/api/client/models/sandbox_logs.py +91 -0
- loopix/api/client/models/sandbox_logs_v2_response.py +73 -0
- loopix/api/client/models/sandbox_metric.py +126 -0
- loopix/api/client/models/sandbox_network_config.py +118 -0
- loopix/api/client/models/sandbox_network_config_rules.py +72 -0
- loopix/api/client/models/sandbox_network_rule.py +74 -0
- loopix/api/client/models/sandbox_network_transform.py +79 -0
- loopix/api/client/models/sandbox_network_transform_headers.py +47 -0
- loopix/api/client/models/sandbox_network_update_config.py +114 -0
- loopix/api/client/models/sandbox_network_update_config_rules.py +71 -0
- loopix/api/client/models/sandbox_on_timeout.py +9 -0
- loopix/api/client/models/sandbox_pause_request.py +62 -0
- loopix/api/client/models/sandbox_state.py +9 -0
- loopix/api/client/models/sandbox_volume_mount.py +67 -0
- loopix/api/client/models/sandboxes_with_metrics.py +59 -0
- loopix/api/client/models/snapshot_info.py +70 -0
- loopix/api/client/models/team.py +83 -0
- loopix/api/client/models/team_api_key.py +158 -0
- loopix/api/client/models/team_metric.py +86 -0
- loopix/api/client/models/team_user.py +75 -0
- loopix/api/client/models/template.py +225 -0
- loopix/api/client/models/template_alias_response.py +67 -0
- loopix/api/client/models/template_build.py +139 -0
- loopix/api/client/models/template_build_file_upload.py +70 -0
- loopix/api/client/models/template_build_info.py +126 -0
- loopix/api/client/models/template_build_logs_response.py +73 -0
- loopix/api/client/models/template_build_request.py +115 -0
- loopix/api/client/models/template_build_request_v2.py +88 -0
- loopix/api/client/models/template_build_request_v3.py +107 -0
- loopix/api/client/models/template_build_start_v2.py +184 -0
- loopix/api/client/models/template_build_status.py +11 -0
- loopix/api/client/models/template_legacy.py +207 -0
- loopix/api/client/models/template_request_response_v3.py +99 -0
- loopix/api/client/models/template_step.py +91 -0
- loopix/api/client/models/template_tag.py +78 -0
- loopix/api/client/models/template_update_request.py +59 -0
- loopix/api/client/models/template_update_response.py +59 -0
- loopix/api/client/models/template_with_builds.py +156 -0
- loopix/api/client/models/update_team_api_key.py +59 -0
- loopix/api/client/models/volume.py +67 -0
- loopix/api/client/models/volume_and_token.py +75 -0
- loopix/api/client/models/volume_token.py +59 -0
- loopix/api/client/py.typed +1 -0
- loopix/api/client/types.py +54 -0
- loopix/api/client_async/__init__.py +74 -0
- loopix/api/client_sync/__init__.py +73 -0
- loopix/api/metadata.py +14 -0
- loopix/connection_config.py +309 -0
- loopix/envd/api.py +170 -0
- loopix/envd/filesystem/filesystem_connect.py +193 -0
- loopix/envd/filesystem/filesystem_pb2.py +80 -0
- loopix/envd/filesystem/filesystem_pb2.pyi +272 -0
- loopix/envd/process/process_connect.py +174 -0
- loopix/envd/process/process_pb2.py +96 -0
- loopix/envd/process/process_pb2.pyi +316 -0
- loopix/envd/rpc.py +139 -0
- loopix/envd/versions.py +11 -0
- loopix/exceptions.py +133 -0
- loopix/io_utils.py +57 -0
- loopix/paginator.py +52 -0
- loopix/py.typed +0 -0
- loopix/sandbox/_git/__init__.py +85 -0
- loopix/sandbox/_git/args.py +363 -0
- loopix/sandbox/_git/auth.py +132 -0
- loopix/sandbox/_git/config.py +32 -0
- loopix/sandbox/_git/parse.py +222 -0
- loopix/sandbox/_git/types.py +149 -0
- loopix/sandbox/commands/command_handle.py +69 -0
- loopix/sandbox/commands/main.py +39 -0
- loopix/sandbox/filesystem/filesystem.py +337 -0
- loopix/sandbox/filesystem/watch_handle.py +70 -0
- loopix/sandbox/main.py +227 -0
- loopix/sandbox/mcp.py +1949 -0
- loopix/sandbox/network.py +8 -0
- loopix/sandbox/sandbox_api.py +624 -0
- loopix/sandbox/signature.py +47 -0
- loopix/sandbox/utils.py +34 -0
- loopix/sandbox_async/commands/command.py +396 -0
- loopix/sandbox_async/commands/command_handle.py +298 -0
- loopix/sandbox_async/commands/pty.py +257 -0
- loopix/sandbox_async/filesystem/filesystem.py +720 -0
- loopix/sandbox_async/filesystem/watch_handle.py +97 -0
- loopix/sandbox_async/git.py +1100 -0
- loopix/sandbox_async/main.py +987 -0
- loopix/sandbox_async/paginator.py +140 -0
- loopix/sandbox_async/sandbox_api.py +504 -0
- loopix/sandbox_async/utils.py +7 -0
- loopix/sandbox_domains.py +5 -0
- loopix/sandbox_sync/commands/command.py +420 -0
- loopix/sandbox_sync/commands/command_handle.py +239 -0
- loopix/sandbox_sync/commands/pty.py +279 -0
- loopix/sandbox_sync/filesystem/filesystem.py +710 -0
- loopix/sandbox_sync/filesystem/watch_handle.py +102 -0
- loopix/sandbox_sync/git.py +1077 -0
- loopix/sandbox_sync/main.py +975 -0
- loopix/sandbox_sync/paginator.py +140 -0
- loopix/sandbox_sync/sandbox_api.py +491 -0
- loopix/template/consts.py +45 -0
- loopix/template/dockerfile_parser.py +286 -0
- loopix/template/logger.py +232 -0
- loopix/template/main.py +1368 -0
- loopix/template/readycmd.py +144 -0
- loopix/template/types.py +194 -0
- loopix/template/utils.py +426 -0
- loopix/template_async/build_api.py +419 -0
- loopix/template_async/main.py +528 -0
- loopix/template_sync/build_api.py +409 -0
- loopix/template_sync/main.py +529 -0
- loopix/volume/client/__init__.py +8 -0
- loopix/volume/client/api/__init__.py +1 -0
- loopix/volume/client/api/volumes/__init__.py +1 -0
- loopix/volume/client/api/volumes/delete_volumecontent_volume_id_path.py +174 -0
- loopix/volume/client/api/volumes/get_volumecontent_volume_id_dir.py +204 -0
- loopix/volume/client/api/volumes/get_volumecontent_volume_id_file.py +179 -0
- loopix/volume/client/api/volumes/get_volumecontent_volume_id_path.py +176 -0
- loopix/volume/client/api/volumes/patch_volumecontent_volume_id_path.py +203 -0
- loopix/volume/client/api/volumes/post_volumecontent_volume_id_dir.py +239 -0
- loopix/volume/client/api/volumes/put_volumecontent_volume_id_file.py +259 -0
- loopix/volume/client/client.py +286 -0
- loopix/volume/client/errors.py +16 -0
- loopix/volume/client/models/__init__.py +13 -0
- loopix/volume/client/models/error.py +67 -0
- loopix/volume/client/models/patch_volumecontent_volume_id_path_body.py +77 -0
- loopix/volume/client/models/volume_entry_stat.py +145 -0
- loopix/volume/client/models/volume_entry_stat_type.py +11 -0
- loopix/volume/client/py.typed +1 -0
- loopix/volume/client/types.py +54 -0
- loopix/volume/client_async/__init__.py +88 -0
- loopix/volume/client_sync/__init__.py +80 -0
- loopix/volume/connection_config.py +145 -0
- loopix/volume/types.py +62 -0
- loopix/volume/utils.py +52 -0
- loopix/volume/volume_async.py +639 -0
- loopix/volume/volume_sync.py +639 -0
- loopix_connect/__init__.py +1 -0
- loopix_connect/client.py +534 -0
- loopix_connect/py.typed +0 -0
- loopix_sdk-2.30.0.dist-info/METADATA +98 -0
- loopix_sdk-2.30.0.dist-info/RECORD +238 -0
- loopix_sdk-2.30.0.dist-info/WHEEL +4 -0
- 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)
|