unikraft-cloud 0.1.0__tar.gz
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.
- unikraft_cloud-0.1.0/LICENSE.md +28 -0
- unikraft_cloud-0.1.0/PKG-INFO +352 -0
- unikraft_cloud-0.1.0/README.md +331 -0
- unikraft_cloud-0.1.0/pyproject.toml +64 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/__init__.py +206 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/__init__.py +53 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/__init__.py +51 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/auth_gen.py +72 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/images_gen.py +40 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/metros_gen.py +34 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/models_gen.py +1006 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/node_activation_service_gen.py +40 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/controlplane/node_service_gen.py +271 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/__init__.py +70 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/autoscale_gen.py +233 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/certificates_gen.py +164 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/images_gen.py +67 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/instances_gen.py +816 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/models_gen.py +6964 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/node_gen.py +34 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/service_groups_gen.py +168 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/users_gen.py +72 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/api/platform/volumes_gen.py +439 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/client.py +270 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/__init__.py +1 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/errors.py +208 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/fanout.py +263 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/handle.py +204 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/handle_set.py +112 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/http.py +553 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/metro.py +141 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/pagination.py +159 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/patch.py +168 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/resource.py +458 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/response.py +300 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/core/session.py +218 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/py.typed +0 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/__init__.py +1 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/_shared.py +139 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/certificates.py +350 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/instances.py +861 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/service_groups.py +401 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/users.py +63 -0
- unikraft_cloud-0.1.0/src/unikraft_cloud/resources/volumes.py +500 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Unikraft GmbH. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unikraft-cloud
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Unikraft Cloud Platform API
|
|
5
|
+
Keywords: unikraft,unikraft-cloud,kraftcloud,sdk,unikernel,cloud,api
|
|
6
|
+
Author: Unikraft GmbH
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
License-File: LICENSE.md
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Requires-Dist: pydantic>=2.7
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Project-URL: Homepage, https://unikraft.com
|
|
18
|
+
Project-URL: Issues, https://github.com/unikraft-cloud/python-sdk/issues
|
|
19
|
+
Project-URL: Repository, https://github.com/unikraft-cloud/python-sdk
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Unikraft Cloud Python SDK
|
|
23
|
+
|
|
24
|
+
The official Python SDK for the [Unikraft Cloud](https://unikraft.com) Platform and
|
|
25
|
+
control-plane APIs.
|
|
26
|
+
|
|
27
|
+
It has two layers. The **idiomatic** layer is what you reach for: envelope-free results,
|
|
28
|
+
automatic pagination, chainable references, and multi-metro fan-out. The **plumbing**
|
|
29
|
+
layer underneath mirrors the OpenAPI specification exactly, and stays available for
|
|
30
|
+
anything the idiomatic layer does not cover yet.
|
|
31
|
+
|
|
32
|
+
The SDK is async-only.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
pip install unikraft-cloud
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Python 3.10 or newer.
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import asyncio
|
|
46
|
+
|
|
47
|
+
from unikraft_cloud import UnikraftCloud
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def main() -> None:
|
|
51
|
+
async with UnikraftCloud() as ukc: # token from UKC_TOKEN
|
|
52
|
+
instance = await ukc.metro("fra").instances.create(
|
|
53
|
+
image="nginx:latest", memory_mb=256, autostart=True
|
|
54
|
+
)
|
|
55
|
+
print(instance.name, instance.uuid, instance.metro)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
asyncio.run(main())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The client owns a connection pool, so close it when you are done — either with
|
|
62
|
+
`async with`, or by awaiting `ukc.aclose()`.
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
ukc = UnikraftCloud(
|
|
68
|
+
token="...", # falls back to UKC_TOKEN
|
|
69
|
+
metro="fra", # falls back to UKC_METRO; omit to cover every metro
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Argument | Purpose |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `token` | Bearer token. Falls back to `UKC_TOKEN`. |
|
|
76
|
+
| `metro` | The metro operations default to, or a full `http(s)://` URL for a staging or self-hosted deployment. Falls back to `UKC_METRO`. A code leaves the other metros reachable with `ukc.metro(...)`; a URL pins the client to that endpoint, and naming another metro then raises. |
|
|
77
|
+
| `metros` | The metros operations cover by default: `"all"`, one metro, or a list. Creating a resource needs exactly one, so name a metro somewhere when you create. |
|
|
78
|
+
| `base_url` | Explicit platform API base URL. It settles where requests go, so it overrides `metro` and `UKC_METRO` alike, and pins the client to that one endpoint. |
|
|
79
|
+
| `control_plane_url` | Override the control-plane API base URL. |
|
|
80
|
+
| `headers` | Extra headers sent with every request. |
|
|
81
|
+
| `user_agent` | Override the default User-Agent. |
|
|
82
|
+
| `http` | An `httpx.AsyncClient` to send through. Supplying one makes its lifetime yours. |
|
|
83
|
+
| `transport` | An `httpx.AsyncBaseTransport`, chiefly for testing with `httpx.MockTransport`. |
|
|
84
|
+
| `trust_env` | Honour `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`. Defaults to `True`. |
|
|
85
|
+
| `timeout` | Timeout for every request. Omitted, an injected `http` client keeps its own; otherwise the default bounds connecting but not reading, because `wait` operations block for as long as you asked. |
|
|
86
|
+
|
|
87
|
+
## Metros
|
|
88
|
+
|
|
89
|
+
The platform API is metro-scoped. By default the client is **account-wide**: reads ask
|
|
90
|
+
every metro the account can reach and merge the answers as they arrive, and each result
|
|
91
|
+
carries the metro it came from.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Every metro, merged as the pages arrive. Await the listing instead for a list.
|
|
95
|
+
async for inst in ukc.instances.list(details=True):
|
|
96
|
+
print(inst.metro, inst.name, inst.state)
|
|
97
|
+
every = await ukc.instances.list(details=True) # one pass each: call list() again for more
|
|
98
|
+
|
|
99
|
+
# One metro. Because it is known, no lookup is needed.
|
|
100
|
+
await ukc.metro("fra").instances.get(name="web").suspend()
|
|
101
|
+
|
|
102
|
+
# Several metros, for one call or for a whole client.
|
|
103
|
+
async for inst in ukc.instances.list(metros=["fra", "dal"]):
|
|
104
|
+
...
|
|
105
|
+
scoped = ukc.metros(["fra", "dal"])
|
|
106
|
+
|
|
107
|
+
# A listing you stop reading holds a page of every metro, so close it.
|
|
108
|
+
async with ukc.instances.list() as listing:
|
|
109
|
+
async for inst in listing:
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
# What the account can reach, as the control plane reports it.
|
|
113
|
+
for endpoint in await ukc.available_metros():
|
|
114
|
+
print(endpoint.metro, endpoint.base_url)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Naming metros is also how you skip metro discovery, which is otherwise one extra request
|
|
118
|
+
per client.
|
|
119
|
+
|
|
120
|
+
## References
|
|
121
|
+
|
|
122
|
+
A resource is addressed by `name` or `uuid` — one or the other, because the API validates
|
|
123
|
+
whichever field it is given.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
await ukc.instances.get(name="web")
|
|
127
|
+
await ukc.instances.get(uuid="550e8400-e29b-41d4-a716-446655440000")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A name is only unique **within** a metro, so the same name can exist in several. Add
|
|
131
|
+
`metro=` to say which you mean, which also saves a lookup:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
await ukc.instances.get(name="web", metro="fra")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Without it, and with more than one metro in scope, the SDK asks every metro. If the name
|
|
138
|
+
matches in several it raises `AmbiguousRefError` rather than picking one — with the
|
|
139
|
+
matches attached, so recovering costs no further requests:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from unikraft_cloud import AmbiguousRefError
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
await ukc.instances.get(name="web")
|
|
146
|
+
except AmbiguousRefError as err:
|
|
147
|
+
print(err.metros) # ("fra", "dal")
|
|
148
|
+
print([m.uuid for m in err.matches])
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
To act on all of them deliberately, use `each()`:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
await ukc.instances.each(name="web").suspend() # in every metro that has one
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Bulk operations take a sequence of references, as `Ref` objects, plain dicts, or names:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from unikraft_cloud import Ref
|
|
161
|
+
|
|
162
|
+
await ukc.instances.delete([Ref(uuid="a"), {"name": "b"}, "web"])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
An operation the API could only carry out in part raises, naming what failed. What did
|
|
166
|
+
succeed is on `err.results`, so a partial failure costs nothing already done:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from unikraft_cloud import NotFoundError
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
await ukc.instances.delete(["web", "gone"])
|
|
173
|
+
except NotFoundError as err:
|
|
174
|
+
print([deleted.name for deleted in err.results]) # ["web"]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Chainable handles
|
|
178
|
+
|
|
179
|
+
Single-resource operations return a **handle** rather than a coroutine, so they compose.
|
|
180
|
+
A handle is awaitable too, so awaiting one gives you the resource:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
inst = await ukc.instances.get(name="web") # the instance
|
|
184
|
+
await ukc.instances.get(name="web").suspend() # the suspend
|
|
185
|
+
|
|
186
|
+
logs = await (
|
|
187
|
+
ukc.metro("fra")
|
|
188
|
+
.instances.create(image="nginx:latest")
|
|
189
|
+
.wait(state="running", timeout_seconds=30)
|
|
190
|
+
.logs(offset=-4096)
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Nothing is sent until a handle is awaited or an operation is chained onto it. With one
|
|
195
|
+
metro in scope, `get(name=...).suspend()` is a single request; when the scope spans
|
|
196
|
+
metros, the instance is located first so the operation reaches the metro that holds it.
|
|
197
|
+
|
|
198
|
+
A handle that is dropped without ever being awaited emits a `RuntimeWarning`: unlike a
|
|
199
|
+
forgotten `await` on a coroutine, nothing else would tell you no request was sent.
|
|
200
|
+
|
|
201
|
+
A handle is awaitable but is not a coroutine, so `asyncio.gather(...)` takes one while
|
|
202
|
+
`asyncio.create_task(...)` does not; wrap it in `asyncio.ensure_future(...)` for a task.
|
|
203
|
+
|
|
204
|
+
## Updating
|
|
205
|
+
|
|
206
|
+
Properties are keyword arguments. A value sets the property, `REMOVE` clears it out, and
|
|
207
|
+
anything omitted is left alone — all in one request.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from unikraft_cloud import REMOVE
|
|
211
|
+
|
|
212
|
+
await ukc.instances.get(name="web").update(memory_mb=512, vcpus=2, autokill=REMOVE)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
When `set` is not what you mean — merging into a property, or removing individual members
|
|
216
|
+
— stage the operations and apply them together:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
await (
|
|
220
|
+
ukc.instances.get(name="web")
|
|
221
|
+
.edit()
|
|
222
|
+
.set(memory_mb=512)
|
|
223
|
+
.add(env={"LOG_LEVEL": "debug"}, tags=["prod"])
|
|
224
|
+
.delete(env=["OLD_FLAG"])
|
|
225
|
+
.apply()
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`apply()` returns a handle, so the chain continues. For anything keyword arguments cannot
|
|
230
|
+
express, `patch()` takes the raw triples.
|
|
231
|
+
|
|
232
|
+
## Errors
|
|
233
|
+
|
|
234
|
+
Every failure is an `UnikraftCloudError`, so one `except` catches the lot. Its `kind`
|
|
235
|
+
says which layer failed (`"http"`, `"network"`, `"parse"` or `"fanout"`) and `status`
|
|
236
|
+
carries the HTTP status where there was one.
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from unikraft_cloud import NotFoundError, UnikraftCloudError
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
await ukc.instances.get(name="web")
|
|
243
|
+
except NotFoundError:
|
|
244
|
+
...
|
|
245
|
+
except UnikraftCloudError as err:
|
|
246
|
+
print(err.kind, err.status, err.errors)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`AuthenticationError` (401/403), `NotFoundError` (404), `AlreadyExistsError` (409),
|
|
250
|
+
`RateLimitError` (429) and `ServerError` (5xx) are raised for the statuses they name, and
|
|
251
|
+
all subclass `UnikraftCloudError`. The API reports some failures inside an otherwise-200
|
|
252
|
+
envelope, per item; those carry the API's own code on `err.errors[n].code` and are raised
|
|
253
|
+
with the status that says the same thing.
|
|
254
|
+
|
|
255
|
+
A `wait()` that runs out of time raises `WaitTimeoutError`, which is also a builtin
|
|
256
|
+
`TimeoutError`, and carries the state the API last saw:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
try:
|
|
260
|
+
await ukc.instances.get(name="web").wait(state="running", timeout_seconds=30)
|
|
261
|
+
except TimeoutError as err:
|
|
262
|
+
print(err.state) # e.g. "starting"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
When the API attaches a warning to an answer -- a deprecated field, say -- the SDK
|
|
266
|
+
issues it as a Python `UnikraftCloudWarning`, so the standard `warnings` filters
|
|
267
|
+
apply.
|
|
268
|
+
|
|
269
|
+
A multi-metro operation that only partly succeeded raises `MetroFanoutError`. An
|
|
270
|
+
iteration yields everything the healthy metros returned *before* raising, so a partial
|
|
271
|
+
failure never costs you the whole answer; operations that cannot yield as they go attach
|
|
272
|
+
what did arrive to `err.results`.
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from unikraft_cloud import MetroFanoutError
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
async for inst in ukc.instances.list():
|
|
279
|
+
...
|
|
280
|
+
except MetroFanoutError as err:
|
|
281
|
+
print([failure.metro for failure in err.failures])
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Resources
|
|
285
|
+
|
|
286
|
+
`instances`, `volumes`, `services`, `certificates` and `users` hang off any scope —
|
|
287
|
+
`ukc`, `ukc.metro("fra")` or `ukc.metros([...])`.
|
|
288
|
+
|
|
289
|
+
Creating one takes the properties the API describes as keyword arguments, and a property
|
|
290
|
+
it does not have is a `TypeError` rather than a field the server quietly ignores.
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
await ukc.volumes.get(name="data").attach(to="web", at="/data")
|
|
294
|
+
await ukc.services.get(name="web").update(hard_limit=10)
|
|
295
|
+
await ukc.certificates.get(name="tls").update(chain=chain_pem, pkey=key_pem)
|
|
296
|
+
|
|
297
|
+
for quota in await ukc.users.quotas():
|
|
298
|
+
print(quota.metro, quota.used, quota.hard)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## The plumbing layer
|
|
302
|
+
|
|
303
|
+
Every operation in the specification is available raw, returning the response envelope
|
|
304
|
+
untouched. Each client talks to exactly one metro, and a single call can be redirected
|
|
305
|
+
with `base_url=`.
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
res = await ukc.api.platform.instances.get_instances(count=10)
|
|
309
|
+
print(res.status, res.op_time_us, res.data.instances)
|
|
310
|
+
|
|
311
|
+
await ukc.api.controlplane.metros.list_metros()
|
|
312
|
+
|
|
313
|
+
# Or per resource, alongside its idiomatic client.
|
|
314
|
+
await ukc.instances.api.get_instance_metrics(uuid=["..."])
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
It can also be used on its own, without the idiomatic layer:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
from unikraft_cloud import ApiClientConfig
|
|
321
|
+
from unikraft_cloud.api.platform import PlatformApi
|
|
322
|
+
|
|
323
|
+
config = ApiClientConfig(base_url="https://api.fra.unikraft.cloud", token=token)
|
|
324
|
+
async with PlatformApi(config) as api:
|
|
325
|
+
res = await api.instances.get_instances(count=10)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Examples
|
|
329
|
+
|
|
330
|
+
- [`examples/quickstart.py`](examples/quickstart.py) — create, wait, read logs, list, suspend, delete
|
|
331
|
+
- [`examples/update.py`](examples/update.py) — patch objects and the staged editor
|
|
332
|
+
- [`examples/plumbing.py`](examples/plumbing.py) — the raw API on its own
|
|
333
|
+
|
|
334
|
+
## Development
|
|
335
|
+
|
|
336
|
+
The `api/platform` and `api/controlplane` packages are generated from the OpenAPI
|
|
337
|
+
specification by [`openapi-gen`](https://github.com/unikraft-cloud) using the templates in
|
|
338
|
+
[`templates/`](templates). Everything else is hand-written. Files ending in `_gen.py` are
|
|
339
|
+
never edited by hand.
|
|
340
|
+
|
|
341
|
+
```sh
|
|
342
|
+
make generate # regenerate both plumbing clients from the specs
|
|
343
|
+
make lint # ruff check + format --check
|
|
344
|
+
make typecheck # mypy
|
|
345
|
+
make test # pytest
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The test suite runs entirely offline through `httpx.MockTransport`.
|
|
349
|
+
|
|
350
|
+
## Licence
|
|
351
|
+
|
|
352
|
+
BSD-3-Clause. See [`LICENSE.md`](LICENSE.md).
|