basaltic-sdk-python 0.1.1__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.
- basaltic/__init__.py +26 -0
- basaltic/_client.py +136 -0
- basaltic/_common.py +284 -0
- basaltic/_transport.py +196 -0
- basaltic/_version.py +3 -0
- basaltic/auth.py +231 -0
- basaltic/client.py +144 -0
- basaltic/config.py +151 -0
- basaltic/errors.py +73 -0
- basaltic/models/__init__.py +17 -0
- basaltic/models/audit.py +85 -0
- basaltic/models/billing.py +284 -0
- basaltic/models/catalog.py +35 -0
- basaltic/models/certificate.py +127 -0
- basaltic/models/compute.py +905 -0
- basaltic/models/dns.py +256 -0
- basaltic/models/iam.py +736 -0
- basaltic/models/kms.py +157 -0
- basaltic/models/loadbalancer.py +574 -0
- basaltic/models/network.py +985 -0
- basaltic/models/quota.py +30 -0
- basaltic/models/secrets.py +136 -0
- basaltic/models/storage.py +789 -0
- basaltic/models/telemetry.py +393 -0
- basaltic/models/workspace.py +734 -0
- basaltic/py.typed +0 -0
- basaltic/response.py +152 -0
- basaltic/services/__init__.py +1 -0
- basaltic/services/audit.py +211 -0
- basaltic/services/billing.py +777 -0
- basaltic/services/catalog.py +172 -0
- basaltic/services/certificate.py +395 -0
- basaltic/services/compute.py +2395 -0
- basaltic/services/dns.py +1079 -0
- basaltic/services/iam.py +3405 -0
- basaltic/services/kms.py +742 -0
- basaltic/services/loadbalancer.py +1727 -0
- basaltic/services/network.py +4691 -0
- basaltic/services/quota.py +75 -0
- basaltic/services/secrets.py +550 -0
- basaltic/services/storage.py +3059 -0
- basaltic/services/telemetry.py +1316 -0
- basaltic/services/workspace.py +3899 -0
- basaltic_sdk_python-0.1.1.dist-info/METADATA +211 -0
- basaltic_sdk_python-0.1.1.dist-info/RECORD +47 -0
- basaltic_sdk_python-0.1.1.dist-info/WHEEL +4 -0
- basaltic_sdk_python-0.1.1.dist-info/licenses/LICENSE +202 -0
basaltic/py.typed
ADDED
|
File without changes
|
basaltic/response.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Response envelopes, pagination and resource reference helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Generic, TypeVar, cast
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .errors import AmbiguousReferenceError, ApiError, ProtocolError
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T", covariant=True)
|
|
15
|
+
Item = TypeVar("Item", covariant=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class ApiResponse(Generic[T]):
|
|
20
|
+
data: T
|
|
21
|
+
response: httpx.Response
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def request_id(self) -> str:
|
|
25
|
+
return str(self.response.headers.get("X-Request-Id", ""))
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def status_code(self) -> int:
|
|
29
|
+
return self.response.status_code
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Page(ApiResponse[T], Generic[T, Item]):
|
|
34
|
+
items_key: str
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def items(self) -> list[Item]:
|
|
38
|
+
if not isinstance(self.data, dict) or not isinstance(self.data.get(self.items_key), list):
|
|
39
|
+
raise ProtocolError("List response is missing its items array.")
|
|
40
|
+
return cast(list[Item], self.data[self.items_key])
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def has_more(self) -> bool:
|
|
44
|
+
meta = self.data.get("meta") if isinstance(self.data, dict) else None
|
|
45
|
+
return isinstance(meta, dict) and meta.get("has_more") is True
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def marker(self) -> str:
|
|
49
|
+
meta = self.data.get("meta") if isinstance(self.data, dict) else None
|
|
50
|
+
value = meta.get("marker") if isinstance(meta, dict) else None
|
|
51
|
+
return value if isinstance(value, str) else ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def iterate_pages(fetch: Callable[[str], Page[Any, Item]], marker: str = "") -> Iterator[Item]:
|
|
55
|
+
seen = {marker}
|
|
56
|
+
while True:
|
|
57
|
+
page = fetch(marker)
|
|
58
|
+
yield from page.items
|
|
59
|
+
if not page.has_more:
|
|
60
|
+
return
|
|
61
|
+
marker = next_marker(page, seen)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def aiterate_pages(
|
|
65
|
+
fetch: Callable[[str], Awaitable[Page[Any, Item]]], marker: str = ""
|
|
66
|
+
) -> AsyncIterator[Item]:
|
|
67
|
+
seen = {marker}
|
|
68
|
+
while True:
|
|
69
|
+
page = await fetch(marker)
|
|
70
|
+
for item in page.items:
|
|
71
|
+
yield item
|
|
72
|
+
if not page.has_more:
|
|
73
|
+
return
|
|
74
|
+
marker = next_marker(page, seen)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def next_marker(page: Page[Any, Any], seen: set[str]) -> str:
|
|
78
|
+
marker = page.marker
|
|
79
|
+
if not marker or marker in seen:
|
|
80
|
+
raise ProtocolError("Pagination did not advance; refusing a truncated or repeated walk.")
|
|
81
|
+
seen.add(marker)
|
|
82
|
+
return marker
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def reference_scope(scope: Mapping[str, object] | None) -> dict[str, object]:
|
|
86
|
+
return {k: v for k, v in (scope or {}).items() if k not in ("name", "crn", "marker")}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def reference_filter(reference: str, has_name: bool) -> dict[str, str] | None:
|
|
90
|
+
if not reference:
|
|
91
|
+
raise ValueError("A nonempty resource reference is required.")
|
|
92
|
+
if re.fullmatch(r"[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}", reference):
|
|
93
|
+
return None
|
|
94
|
+
kind = "crn" if reference.startswith("crn:") else "name"
|
|
95
|
+
if kind == "crn" and len(reference.split(":")) != 5:
|
|
96
|
+
raise ValueError("A CRN must have five colon-separated segments.")
|
|
97
|
+
if kind == "name" and not has_name:
|
|
98
|
+
raise ValueError("This resource requires a UUID or CRN.")
|
|
99
|
+
return {kind: reference}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def unwrap_reference(result: ApiResponse[Any], envelope: str | None) -> ApiResponse[Any]:
|
|
103
|
+
data = result.data.get(envelope) if envelope and isinstance(result.data, dict) else result.data
|
|
104
|
+
if not isinstance(data, dict):
|
|
105
|
+
raise ProtocolError("Reference lookup is missing its resource envelope.")
|
|
106
|
+
return ApiResponse(data, result.response)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def select_reference(page: Page[Any, Any]) -> ApiResponse[Any]:
|
|
110
|
+
if len(page.items) > 1 or page.has_more:
|
|
111
|
+
raise AmbiguousReferenceError()
|
|
112
|
+
if not page.items:
|
|
113
|
+
raise ApiError(
|
|
114
|
+
404,
|
|
115
|
+
"REFERENCE_NOT_FOUND",
|
|
116
|
+
"No resource matches the reference.",
|
|
117
|
+
page.request_id,
|
|
118
|
+
"resolve_reference",
|
|
119
|
+
)
|
|
120
|
+
if not isinstance(page.items[0], dict):
|
|
121
|
+
raise ProtocolError("Reference lookup returned a non-object resource.")
|
|
122
|
+
return ApiResponse(page.items[0], page.response)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def resolve_reference(
|
|
126
|
+
reference: str,
|
|
127
|
+
get: Callable[[str], ApiResponse[Any]],
|
|
128
|
+
listing: Callable[[dict[str, str]], Page[Any, Any]],
|
|
129
|
+
has_name: bool,
|
|
130
|
+
envelope: str | None = None,
|
|
131
|
+
) -> ApiResponse[Any]:
|
|
132
|
+
match = reference_filter(reference, has_name)
|
|
133
|
+
return (
|
|
134
|
+
unwrap_reference(get(reference), envelope)
|
|
135
|
+
if match is None
|
|
136
|
+
else select_reference(listing(match))
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async def aresolve_reference(
|
|
141
|
+
reference: str,
|
|
142
|
+
get: Callable[[str], Awaitable[ApiResponse[Any]]],
|
|
143
|
+
listing: Callable[[dict[str, str]], Awaitable[Page[Any, Any]]],
|
|
144
|
+
has_name: bool,
|
|
145
|
+
envelope: str | None = None,
|
|
146
|
+
) -> ApiResponse[Any]:
|
|
147
|
+
match = reference_filter(reference, has_name)
|
|
148
|
+
return (
|
|
149
|
+
unwrap_reference(await get(reference), envelope)
|
|
150
|
+
if match is None
|
|
151
|
+
else select_reference(await listing(match))
|
|
152
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Generated synchronous and asynchronous service clients."""
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Generated typed API methods; do not edit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator, Iterator
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from .._common import UNSET, Operation
|
|
9
|
+
from .._transport import AsyncTransport, SyncTransport
|
|
10
|
+
from ..config import RequestOptions
|
|
11
|
+
from ..models import audit as m
|
|
12
|
+
from ..response import (
|
|
13
|
+
ApiResponse,
|
|
14
|
+
Page,
|
|
15
|
+
aiterate_pages,
|
|
16
|
+
aresolve_reference,
|
|
17
|
+
iterate_pages,
|
|
18
|
+
reference_scope,
|
|
19
|
+
resolve_reference,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_OPS = {
|
|
23
|
+
"getAuditLog": Operation(
|
|
24
|
+
id="getAuditLog",
|
|
25
|
+
method="GET",
|
|
26
|
+
path="/v1/audit-logs/{log_id}",
|
|
27
|
+
authenticated=True,
|
|
28
|
+
requiredQuery=[],
|
|
29
|
+
requiredHeaders=[],
|
|
30
|
+
queryEncoding={},
|
|
31
|
+
bodyRequired=False,
|
|
32
|
+
contentType="",
|
|
33
|
+
accept="application/json",
|
|
34
|
+
),
|
|
35
|
+
"listAuditLogs": Operation(
|
|
36
|
+
id="listAuditLogs",
|
|
37
|
+
method="GET",
|
|
38
|
+
path="/v1/audit-logs",
|
|
39
|
+
authenticated=True,
|
|
40
|
+
requiredQuery=[],
|
|
41
|
+
requiredHeaders=[],
|
|
42
|
+
queryEncoding={
|
|
43
|
+
"crn": {"style": "form", "explode": True},
|
|
44
|
+
"actor": {"style": "form", "explode": True},
|
|
45
|
+
"actor_type": {"style": "form", "explode": True},
|
|
46
|
+
"action": {"style": "form", "explode": True},
|
|
47
|
+
"resource_type": {"style": "form", "explode": True},
|
|
48
|
+
"resource": {"style": "form", "explode": True},
|
|
49
|
+
"status": {"style": "form", "explode": True},
|
|
50
|
+
"ip_address": {"style": "form", "explode": True},
|
|
51
|
+
"from": {"style": "form", "explode": True},
|
|
52
|
+
"to": {"style": "form", "explode": True},
|
|
53
|
+
"limit": {"style": "form", "explode": True},
|
|
54
|
+
"marker": {"style": "form", "explode": True},
|
|
55
|
+
},
|
|
56
|
+
bodyRequired=False,
|
|
57
|
+
contentType="",
|
|
58
|
+
accept="application/json",
|
|
59
|
+
itemsKey="audit_logs",
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AuditService:
|
|
65
|
+
def __init__(self, transport: SyncTransport) -> None:
|
|
66
|
+
self._transport = transport
|
|
67
|
+
|
|
68
|
+
def get_audit_log(
|
|
69
|
+
self, log_id: str, *, options: RequestOptions | None = None
|
|
70
|
+
) -> ApiResponse[m.GetAuditLogResponse]:
|
|
71
|
+
"Get audit log entry"
|
|
72
|
+
return cast(
|
|
73
|
+
ApiResponse[m.GetAuditLogResponse],
|
|
74
|
+
self._transport.request(
|
|
75
|
+
"audit",
|
|
76
|
+
"https://audit.basaltic.sh",
|
|
77
|
+
_OPS["getAuditLog"],
|
|
78
|
+
"json",
|
|
79
|
+
{"log_id": log_id},
|
|
80
|
+
UNSET,
|
|
81
|
+
None,
|
|
82
|
+
options,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def get_audit_log_by_reference(
|
|
87
|
+
self,
|
|
88
|
+
reference: str,
|
|
89
|
+
*,
|
|
90
|
+
scope: m.GetAuditLogScope | None = None,
|
|
91
|
+
options: RequestOptions | None = None,
|
|
92
|
+
) -> ApiResponse[m.GetAuditLogResource]:
|
|
93
|
+
return cast(
|
|
94
|
+
ApiResponse[m.GetAuditLogResource],
|
|
95
|
+
resolve_reference(
|
|
96
|
+
reference,
|
|
97
|
+
lambda id: self.get_audit_log(id, options=options),
|
|
98
|
+
lambda match: self.list_audit_logs(
|
|
99
|
+
query=cast(
|
|
100
|
+
m.ListAuditLogsQuery, {**reference_scope(scope), **match, "limit": 2}
|
|
101
|
+
),
|
|
102
|
+
options=options,
|
|
103
|
+
),
|
|
104
|
+
False,
|
|
105
|
+
"audit_log",
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def list_audit_logs(
|
|
110
|
+
self, *, query: m.ListAuditLogsQuery | None = None, options: RequestOptions | None = None
|
|
111
|
+
) -> Page[m.ListAuditLogsResponse, m.ListAuditLogsItem]:
|
|
112
|
+
"List audit logs"
|
|
113
|
+
return cast(
|
|
114
|
+
Page[m.ListAuditLogsResponse, m.ListAuditLogsItem],
|
|
115
|
+
self._transport.request(
|
|
116
|
+
"audit",
|
|
117
|
+
"https://audit.basaltic.sh",
|
|
118
|
+
_OPS["listAuditLogs"],
|
|
119
|
+
"page",
|
|
120
|
+
{},
|
|
121
|
+
UNSET,
|
|
122
|
+
query,
|
|
123
|
+
options,
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def list_audit_logs_all(
|
|
128
|
+
self, *, query: m.ListAuditLogsQuery | None = None, options: RequestOptions | None = None
|
|
129
|
+
) -> Iterator[m.ListAuditLogsItem]:
|
|
130
|
+
return iterate_pages(
|
|
131
|
+
lambda marker: self.list_audit_logs(
|
|
132
|
+
query=cast(m.ListAuditLogsQuery, {**(query or {}), "marker": marker}),
|
|
133
|
+
options=options,
|
|
134
|
+
),
|
|
135
|
+
(query or {}).get("marker", ""),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AsyncAuditService:
|
|
140
|
+
def __init__(self, transport: AsyncTransport) -> None:
|
|
141
|
+
self._transport = transport
|
|
142
|
+
|
|
143
|
+
async def get_audit_log(
|
|
144
|
+
self, log_id: str, *, options: RequestOptions | None = None
|
|
145
|
+
) -> ApiResponse[m.GetAuditLogResponse]:
|
|
146
|
+
"Get audit log entry"
|
|
147
|
+
return cast(
|
|
148
|
+
ApiResponse[m.GetAuditLogResponse],
|
|
149
|
+
await self._transport.request(
|
|
150
|
+
"audit",
|
|
151
|
+
"https://audit.basaltic.sh",
|
|
152
|
+
_OPS["getAuditLog"],
|
|
153
|
+
"json",
|
|
154
|
+
{"log_id": log_id},
|
|
155
|
+
UNSET,
|
|
156
|
+
None,
|
|
157
|
+
options,
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
async def get_audit_log_by_reference(
|
|
162
|
+
self,
|
|
163
|
+
reference: str,
|
|
164
|
+
*,
|
|
165
|
+
scope: m.GetAuditLogScope | None = None,
|
|
166
|
+
options: RequestOptions | None = None,
|
|
167
|
+
) -> ApiResponse[m.GetAuditLogResource]:
|
|
168
|
+
return cast(
|
|
169
|
+
ApiResponse[m.GetAuditLogResource],
|
|
170
|
+
await aresolve_reference(
|
|
171
|
+
reference,
|
|
172
|
+
lambda id: self.get_audit_log(id, options=options),
|
|
173
|
+
lambda match: self.list_audit_logs(
|
|
174
|
+
query=cast(
|
|
175
|
+
m.ListAuditLogsQuery, {**reference_scope(scope), **match, "limit": 2}
|
|
176
|
+
),
|
|
177
|
+
options=options,
|
|
178
|
+
),
|
|
179
|
+
False,
|
|
180
|
+
"audit_log",
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
async def list_audit_logs(
|
|
185
|
+
self, *, query: m.ListAuditLogsQuery | None = None, options: RequestOptions | None = None
|
|
186
|
+
) -> Page[m.ListAuditLogsResponse, m.ListAuditLogsItem]:
|
|
187
|
+
"List audit logs"
|
|
188
|
+
return cast(
|
|
189
|
+
Page[m.ListAuditLogsResponse, m.ListAuditLogsItem],
|
|
190
|
+
await self._transport.request(
|
|
191
|
+
"audit",
|
|
192
|
+
"https://audit.basaltic.sh",
|
|
193
|
+
_OPS["listAuditLogs"],
|
|
194
|
+
"page",
|
|
195
|
+
{},
|
|
196
|
+
UNSET,
|
|
197
|
+
query,
|
|
198
|
+
options,
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def list_audit_logs_all(
|
|
203
|
+
self, *, query: m.ListAuditLogsQuery | None = None, options: RequestOptions | None = None
|
|
204
|
+
) -> AsyncIterator[m.ListAuditLogsItem]:
|
|
205
|
+
return aiterate_pages(
|
|
206
|
+
lambda marker: self.list_audit_logs(
|
|
207
|
+
query=cast(m.ListAuditLogsQuery, {**(query or {}), "marker": marker}),
|
|
208
|
+
options=options,
|
|
209
|
+
),
|
|
210
|
+
(query or {}).get("marker", ""),
|
|
211
|
+
)
|