vertracloud-api 0.3.2__tar.gz → 0.3.3__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.
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/CHANGELOG.md +8 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/PKG-INFO +54 -4
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/README.md +53 -3
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/pyproject.toml +1 -1
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/__init__.py +4 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/client.py +5 -1
- vertracloud_api-0.3.3/src/vertracloud/errcodes.py +65 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/errors.py +35 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/apps.py +34 -9
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/databases.py +8 -3
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/images.py +4 -0
- vertracloud_api-0.3.3/src/vertracloud/resources/stacks.py +324 -0
- vertracloud_api-0.3.3/src/vertracloud/resources/templates.py +542 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/workspaces.py +53 -2
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/types.py +1135 -8
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/.gitignore +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/LICENSE +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/py.typed +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/__init__.py +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/account.py +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/billing.py +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/blob.py +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/resources/snapshots.py +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/sse.py +0 -0
- {vertracloud_api-0.3.2 → vertracloud_api-0.3.3}/src/vertracloud/transport.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.3](https://github.com/vertracloud/sdk-api-py/compare/v0.3.2...v0.3.3) (2026-10-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add locked_deploy_settings to applications ([6294bc1](https://github.com/vertracloud/sdk-api-py/commit/6294bc11ae5e1471ef68c997dbf875c8efdd4a69))
|
|
9
|
+
* add templates and stacks resources ([f638dde](https://github.com/vertracloud/sdk-api-py/commit/f638dde5c4df10070f5a0485fa5e3f1fafae02d1))
|
|
10
|
+
|
|
3
11
|
## [0.3.2](https://github.com/vertracloud/sdk-api-py/compare/v0.3.1...v0.3.2) (2026-10-25)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vertracloud-api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Official Python SDK for the public API of Vertra Cloud, a Brazilian application hosting platform (PaaS).
|
|
5
5
|
Project-URL: Homepage, https://vertracloud.app
|
|
6
6
|
Project-URL: Documentation, https://docs.vertracloud.app/sdks
|
|
@@ -22,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
[](https://pypi.org/project/vertracloud-api/)
|
|
23
23
|
[](LICENSE)
|
|
24
24
|
|
|
25
|
-
Official Python SDK for the [Vertra Cloud](https://vertracloud.app) public API: apps, databases, snapshots, account, workspaces and billing — one typed method per route.
|
|
25
|
+
Official Python SDK for the [Vertra Cloud](https://vertracloud.app) public API: apps, databases, templates, stacks, snapshots, account, workspaces and billing — one typed method per route.
|
|
26
26
|
|
|
27
27
|
- Python 3.10+, fully typed (`py.typed`), with `TypedDict` payloads.
|
|
28
28
|
- Zero dependencies outside the standard library.
|
|
@@ -76,7 +76,7 @@ client = VertraClient(
|
|
|
76
76
|
|
|
77
77
|
### Per-call options
|
|
78
78
|
|
|
79
|
-
Methods on apps, databases and
|
|
79
|
+
Methods on apps, databases, images, templates and stacks take a keyword-only `workspace_id`; downloads, uploads and streams also take `timeout`:
|
|
80
80
|
|
|
81
81
|
```python
|
|
82
82
|
# Act on a resource that belongs to a workspace
|
|
@@ -264,6 +264,52 @@ client.images.update(image["id"], {"description": None})
|
|
|
264
264
|
client.images.delete(image["id"], confirm_name="meu-projeto") # {"deleted_apps": [...]}
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
+
### Templates
|
|
268
|
+
|
|
269
|
+
A template describes what to create: one application, or a stack with applications, databases and a Blob folder. `deploy` returns a job right away; follow it with `deploys.events` (Server-Sent Events) or read `deploys.get`. A job that fails is undone as a whole.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
page = client.templates.list(category="bots", sort="popular")
|
|
273
|
+
|
|
274
|
+
job = client.templates.deploy(
|
|
275
|
+
"n8n",
|
|
276
|
+
{"name": "my-n8n", "inputs": {"admin_email": "me@example.com"}},
|
|
277
|
+
idempotency_key="deploy-my-n8n-1", # repeating the call returns the same job
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
events = client.templates.deploys.events(job["id"])
|
|
281
|
+
for event in events: # heartbeats are skipped; the loop ends when the API sends `end`
|
|
282
|
+
print(event["status"], event["current_step"])
|
|
283
|
+
job = client.templates.deploys.get(job["id"]) # outputs are read here, never on the stream
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`idempotency_key` is sent as the `Idempotency-Key` header and never goes in the body: the same key with the same request returns the same job, the same key with another request fails with `IDEMPOTENCY_KEY_CONFLICT`. Outputs marked `once` come back on the first read of a finished job and are empty afterwards.
|
|
287
|
+
|
|
288
|
+
Private templates: `create` and `new_version` take the manifest and one ZIP per service that runs code (`sources={"web": zip_bytes}`); `from_app` and `from_stack` save what you already run (read `app_draft` / `stack_draft` first to review the variables). `share_links` creates, lists and revokes links; `share` previews, uses, copies or reports what a link gives. `updates` reads and runs the update of an app or stack that came from a template, and detaches it. `validate` checks a manifest without creating anything; the findings of a refused one are in `err.manifest_issues`.
|
|
289
|
+
|
|
290
|
+
An application that came from an open template stops receiving versions when its code is changed. The calls that change code (`apps.files.write`, `move`, `delete`, `upload` and `apps.deploys.webhook.create`) fail with `TEMPLATE_DETACH_CONFIRMATION_REQUIRED` until you pass `confirm_detach=True` (in the body of `move` and `webhook.create`); `err.detach_action` tells which action asked for it.
|
|
291
|
+
|
|
292
|
+
### Stacks
|
|
293
|
+
|
|
294
|
+
A stack groups applications, databases and Blob folders, and keeps the connection variables of each application up to date. Changes that alter variables or certificates return `restart_required` and `apps_to_restart`: an application reads them only when it starts, so the restart is yours to ask for. The same notice comes back from `databases.credentials.certificate.reset` and `password.reset`.
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
change = client.stacks.create(
|
|
298
|
+
{
|
|
299
|
+
"name": "shop",
|
|
300
|
+
"members": [
|
|
301
|
+
{"resource_type": "application", "resource_id": app_id},
|
|
302
|
+
{"resource_type": "database", "resource_id": db_id},
|
|
303
|
+
],
|
|
304
|
+
"bindings": [{"app_id": app_id, "database_id": db_id}],
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
if change["restart_required"]:
|
|
308
|
+
print("restart:", change["apps_to_restart"])
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`delete` needs `confirm_name` equal to the stack name; mode `dissolve` removes only the stack, `delete_all` deletes the members too. `members`, `bindings` and `snapshots` group the routes of members, connections and whole-stack snapshots; `connection` returns code to connect an app to what it is linked to. Move a stack or a template to a workspace with `workspaces.stacks` and `workspaces.templates`. The error codes of both areas are constants in `vertracloud.errcodes`.
|
|
312
|
+
|
|
267
313
|
### Database versions
|
|
268
314
|
|
|
269
315
|
```python
|
|
@@ -291,6 +337,8 @@ except VertraAPIError as err:
|
|
|
291
337
|
print(err.status, err.code, err)
|
|
292
338
|
```
|
|
293
339
|
|
|
340
|
+
A few errors carry a structured `details`; tolerant properties read them and give an empty value for any other error: `err.issues` (`DOCKERFILE_REFUSED`), `err.manifest_issues` (`TEMPLATE_MANIFEST_INVALID`), `err.secret_files` (`TEMPLATE_SECRET_IN_SOURCE`), `err.update_blocked` (`TEMPLATE_UPDATE_BLOCKED`) and `err.detach_action` (`TEMPLATE_DETACH_CONFIRMATION_REQUIRED`).
|
|
341
|
+
|
|
294
342
|
Other subclasses: `AuthenticationError` (401), `ScopeDeniedError` (403 — including a missing key scope) and `ValidationError` (400/422). The error codes are listed in the [API reference](https://docs.vertracloud.app/api-reference/introduction).
|
|
295
343
|
|
|
296
344
|
### Testing your code
|
|
@@ -308,9 +356,11 @@ client = VertraClient(api_key="test", transport=my_fake_transport)
|
|
|
308
356
|
| Apps | `client.apps` (+ `.deploys`, `.releases`, `.domains`, `.network`, `.envs`, `.files`) | 43 |
|
|
309
357
|
| Databases | `client.databases` (+ `.credentials`) | 14 |
|
|
310
358
|
| Custom images | `client.images` | 12 |
|
|
359
|
+
| Templates | `client.templates` (+ `.deploys`, `.share_links`, `.share`, `.updates`) | 32 |
|
|
360
|
+
| Stacks | `client.stacks` (+ `.members`, `.bindings`, `.snapshots`) | 20 |
|
|
311
361
|
| Snapshots | `client.snapshots` | 5 |
|
|
312
362
|
| Account | `client.account` (+ `.sessions`, `.folders`, `.favorites`) | 10 |
|
|
313
|
-
| Workspaces | `client.workspaces` (+ `.members`, `.roles`, `.invites`, `.action_requests`, `.apps`, `.databases`, `.folders`, `.favorites`) |
|
|
363
|
+
| Workspaces | `client.workspaces` (+ `.members`, `.roles`, `.invites`, `.action_requests`, `.apps`, `.databases`, `.templates`, `.stacks`, `.folders`, `.favorites`) | 34 |
|
|
314
364
|
| Billing | `client.billing` (+ `.orders`) | 7 |
|
|
315
365
|
| Blob Storage | `client.blob` (+ `.shares`, `.rules`, `.domains`) | own host |
|
|
316
366
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/vertracloud-api/)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
Official Python SDK for the [Vertra Cloud](https://vertracloud.app) public API: apps, databases, snapshots, account, workspaces and billing — one typed method per route.
|
|
6
|
+
Official Python SDK for the [Vertra Cloud](https://vertracloud.app) public API: apps, databases, templates, stacks, snapshots, account, workspaces and billing — one typed method per route.
|
|
7
7
|
|
|
8
8
|
- Python 3.10+, fully typed (`py.typed`), with `TypedDict` payloads.
|
|
9
9
|
- Zero dependencies outside the standard library.
|
|
@@ -57,7 +57,7 @@ client = VertraClient(
|
|
|
57
57
|
|
|
58
58
|
### Per-call options
|
|
59
59
|
|
|
60
|
-
Methods on apps, databases and
|
|
60
|
+
Methods on apps, databases, images, templates and stacks take a keyword-only `workspace_id`; downloads, uploads and streams also take `timeout`:
|
|
61
61
|
|
|
62
62
|
```python
|
|
63
63
|
# Act on a resource that belongs to a workspace
|
|
@@ -245,6 +245,52 @@ client.images.update(image["id"], {"description": None})
|
|
|
245
245
|
client.images.delete(image["id"], confirm_name="meu-projeto") # {"deleted_apps": [...]}
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
+
### Templates
|
|
249
|
+
|
|
250
|
+
A template describes what to create: one application, or a stack with applications, databases and a Blob folder. `deploy` returns a job right away; follow it with `deploys.events` (Server-Sent Events) or read `deploys.get`. A job that fails is undone as a whole.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
page = client.templates.list(category="bots", sort="popular")
|
|
254
|
+
|
|
255
|
+
job = client.templates.deploy(
|
|
256
|
+
"n8n",
|
|
257
|
+
{"name": "my-n8n", "inputs": {"admin_email": "me@example.com"}},
|
|
258
|
+
idempotency_key="deploy-my-n8n-1", # repeating the call returns the same job
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
events = client.templates.deploys.events(job["id"])
|
|
262
|
+
for event in events: # heartbeats are skipped; the loop ends when the API sends `end`
|
|
263
|
+
print(event["status"], event["current_step"])
|
|
264
|
+
job = client.templates.deploys.get(job["id"]) # outputs are read here, never on the stream
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`idempotency_key` is sent as the `Idempotency-Key` header and never goes in the body: the same key with the same request returns the same job, the same key with another request fails with `IDEMPOTENCY_KEY_CONFLICT`. Outputs marked `once` come back on the first read of a finished job and are empty afterwards.
|
|
268
|
+
|
|
269
|
+
Private templates: `create` and `new_version` take the manifest and one ZIP per service that runs code (`sources={"web": zip_bytes}`); `from_app` and `from_stack` save what you already run (read `app_draft` / `stack_draft` first to review the variables). `share_links` creates, lists and revokes links; `share` previews, uses, copies or reports what a link gives. `updates` reads and runs the update of an app or stack that came from a template, and detaches it. `validate` checks a manifest without creating anything; the findings of a refused one are in `err.manifest_issues`.
|
|
270
|
+
|
|
271
|
+
An application that came from an open template stops receiving versions when its code is changed. The calls that change code (`apps.files.write`, `move`, `delete`, `upload` and `apps.deploys.webhook.create`) fail with `TEMPLATE_DETACH_CONFIRMATION_REQUIRED` until you pass `confirm_detach=True` (in the body of `move` and `webhook.create`); `err.detach_action` tells which action asked for it.
|
|
272
|
+
|
|
273
|
+
### Stacks
|
|
274
|
+
|
|
275
|
+
A stack groups applications, databases and Blob folders, and keeps the connection variables of each application up to date. Changes that alter variables or certificates return `restart_required` and `apps_to_restart`: an application reads them only when it starts, so the restart is yours to ask for. The same notice comes back from `databases.credentials.certificate.reset` and `password.reset`.
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
change = client.stacks.create(
|
|
279
|
+
{
|
|
280
|
+
"name": "shop",
|
|
281
|
+
"members": [
|
|
282
|
+
{"resource_type": "application", "resource_id": app_id},
|
|
283
|
+
{"resource_type": "database", "resource_id": db_id},
|
|
284
|
+
],
|
|
285
|
+
"bindings": [{"app_id": app_id, "database_id": db_id}],
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
if change["restart_required"]:
|
|
289
|
+
print("restart:", change["apps_to_restart"])
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`delete` needs `confirm_name` equal to the stack name; mode `dissolve` removes only the stack, `delete_all` deletes the members too. `members`, `bindings` and `snapshots` group the routes of members, connections and whole-stack snapshots; `connection` returns code to connect an app to what it is linked to. Move a stack or a template to a workspace with `workspaces.stacks` and `workspaces.templates`. The error codes of both areas are constants in `vertracloud.errcodes`.
|
|
293
|
+
|
|
248
294
|
### Database versions
|
|
249
295
|
|
|
250
296
|
```python
|
|
@@ -272,6 +318,8 @@ except VertraAPIError as err:
|
|
|
272
318
|
print(err.status, err.code, err)
|
|
273
319
|
```
|
|
274
320
|
|
|
321
|
+
A few errors carry a structured `details`; tolerant properties read them and give an empty value for any other error: `err.issues` (`DOCKERFILE_REFUSED`), `err.manifest_issues` (`TEMPLATE_MANIFEST_INVALID`), `err.secret_files` (`TEMPLATE_SECRET_IN_SOURCE`), `err.update_blocked` (`TEMPLATE_UPDATE_BLOCKED`) and `err.detach_action` (`TEMPLATE_DETACH_CONFIRMATION_REQUIRED`).
|
|
322
|
+
|
|
275
323
|
Other subclasses: `AuthenticationError` (401), `ScopeDeniedError` (403 — including a missing key scope) and `ValidationError` (400/422). The error codes are listed in the [API reference](https://docs.vertracloud.app/api-reference/introduction).
|
|
276
324
|
|
|
277
325
|
### Testing your code
|
|
@@ -289,9 +337,11 @@ client = VertraClient(api_key="test", transport=my_fake_transport)
|
|
|
289
337
|
| Apps | `client.apps` (+ `.deploys`, `.releases`, `.domains`, `.network`, `.envs`, `.files`) | 43 |
|
|
290
338
|
| Databases | `client.databases` (+ `.credentials`) | 14 |
|
|
291
339
|
| Custom images | `client.images` | 12 |
|
|
340
|
+
| Templates | `client.templates` (+ `.deploys`, `.share_links`, `.share`, `.updates`) | 32 |
|
|
341
|
+
| Stacks | `client.stacks` (+ `.members`, `.bindings`, `.snapshots`) | 20 |
|
|
292
342
|
| Snapshots | `client.snapshots` | 5 |
|
|
293
343
|
| Account | `client.account` (+ `.sessions`, `.folders`, `.favorites`) | 10 |
|
|
294
|
-
| Workspaces | `client.workspaces` (+ `.members`, `.roles`, `.invites`, `.action_requests`, `.apps`, `.databases`, `.folders`, `.favorites`) |
|
|
344
|
+
| Workspaces | `client.workspaces` (+ `.members`, `.roles`, `.invites`, `.action_requests`, `.apps`, `.databases`, `.templates`, `.stacks`, `.folders`, `.favorites`) | 34 |
|
|
295
345
|
| Billing | `client.billing` (+ `.orders`) | 7 |
|
|
296
346
|
| Blob Storage | `client.blob` (+ `.shares`, `.rules`, `.domains`) | own host |
|
|
297
347
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vertracloud-api"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
description = "Official Python SDK for the public API of Vertra Cloud, a Brazilian application hosting platform (PaaS)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Typed Python client for the Vertra Cloud public API (`import vertracloud`)."""
|
|
2
2
|
|
|
3
|
+
from . import errcodes
|
|
3
4
|
from .client import VertraClient, __version__
|
|
4
5
|
from .errors import (
|
|
5
6
|
AppBuildFailedError,
|
|
@@ -12,6 +13,7 @@ from .errors import (
|
|
|
12
13
|
VertraAPIError,
|
|
13
14
|
WaitTimeoutError,
|
|
14
15
|
)
|
|
16
|
+
from .resources.templates import TemplateDeployEventStream
|
|
15
17
|
from .sse import SseEvent, SseStream
|
|
16
18
|
from .transport import Response, Transport, UrllibTransport
|
|
17
19
|
|
|
@@ -29,6 +31,8 @@ __all__ = [
|
|
|
29
31
|
"RateLimitError",
|
|
30
32
|
"SseEvent",
|
|
31
33
|
"SseStream",
|
|
34
|
+
"TemplateDeployEventStream",
|
|
35
|
+
"errcodes",
|
|
32
36
|
"Transport",
|
|
33
37
|
"UrllibTransport",
|
|
34
38
|
"Response",
|
|
@@ -16,7 +16,7 @@ from .sse import SseStream
|
|
|
16
16
|
from .transport import Response, Transport, UrllibTransport
|
|
17
17
|
from .types import JsonValue
|
|
18
18
|
|
|
19
|
-
__version__ = "0.3.
|
|
19
|
+
__version__ = "0.3.3" # x-release-please-version
|
|
20
20
|
|
|
21
21
|
DEFAULT_BASE_URL = "https://api.vertracloud.app"
|
|
22
22
|
DEFAULT_BLOB_BASE_URL = "https://blob.vertracloud.app/v1"
|
|
@@ -138,11 +138,15 @@ class VertraClient:
|
|
|
138
138
|
from .resources.databases import DatabasesResource
|
|
139
139
|
from .resources.images import ImagesResource
|
|
140
140
|
from .resources.snapshots import SnapshotsResource
|
|
141
|
+
from .resources.stacks import StacksResource
|
|
142
|
+
from .resources.templates import TemplatesResource
|
|
141
143
|
from .resources.workspaces import WorkspacesResource
|
|
142
144
|
|
|
143
145
|
self.apps = AppsResource(self)
|
|
144
146
|
self.databases = DatabasesResource(self)
|
|
145
147
|
self.images = ImagesResource(self)
|
|
148
|
+
self.templates = TemplatesResource(self)
|
|
149
|
+
self.stacks = StacksResource(self)
|
|
146
150
|
self.snapshots = SnapshotsResource(self)
|
|
147
151
|
self.account = AccountResource(self)
|
|
148
152
|
self.workspaces = WorkspacesResource(self)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Error codes of the template and stack routes, as found in `VertraAPIError.code`.
|
|
2
|
+
|
|
3
|
+
Compare with the constants instead of retyping the strings::
|
|
4
|
+
|
|
5
|
+
from vertracloud import errcodes
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
client.templates.deploy("n8n", {"name": "my-n8n"})
|
|
9
|
+
except VertraAPIError as err:
|
|
10
|
+
if err.code == errcodes.TEMPLATE_RAM_INSUFFICIENT:
|
|
11
|
+
...
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
# -- templates
|
|
17
|
+
TEMPLATE_NOT_FOUND = "TEMPLATE_NOT_FOUND"
|
|
18
|
+
TEMPLATE_VERSION_NOT_FOUND = "TEMPLATE_VERSION_NOT_FOUND"
|
|
19
|
+
TEMPLATE_SLUG_TAKEN = "TEMPLATE_SLUG_TAKEN"
|
|
20
|
+
TEMPLATE_MANIFEST_INVALID = "TEMPLATE_MANIFEST_INVALID" # `VertraAPIError.manifest_issues`
|
|
21
|
+
TEMPLATE_INPUT_INVALID = "TEMPLATE_INPUT_INVALID" # `details.issues` lists the questions
|
|
22
|
+
TEMPLATE_SOURCE_REQUIRED = "TEMPLATE_SOURCE_REQUIRED"
|
|
23
|
+
TEMPLATE_SECRET_IN_SOURCE = "TEMPLATE_SECRET_IN_SOURCE" # `VertraAPIError.secret_files`
|
|
24
|
+
TEMPLATE_GITHUB_MANIFEST_NOT_FOUND = "TEMPLATE_GITHUB_MANIFEST_NOT_FOUND"
|
|
25
|
+
TEMPLATE_PLAN_REQUIRED = "TEMPLATE_PLAN_REQUIRED"
|
|
26
|
+
TEMPLATE_RAM_INSUFFICIENT = "TEMPLATE_RAM_INSUFFICIENT"
|
|
27
|
+
TEMPLATE_IMAGE_SLOTS_EXHAUSTED = "TEMPLATE_IMAGE_SLOTS_EXHAUSTED"
|
|
28
|
+
TEMPLATE_CLOSED = "TEMPLATE_CLOSED"
|
|
29
|
+
TEMPLATE_SETTING_LOCKED = "TEMPLATE_SETTING_LOCKED"
|
|
30
|
+
TEMPLATE_PRIVATE_LIMIT_REACHED = "TEMPLATE_PRIVATE_LIMIT_REACHED"
|
|
31
|
+
TEMPLATE_DEPLOY_IN_PROGRESS = "TEMPLATE_DEPLOY_IN_PROGRESS"
|
|
32
|
+
TEMPLATE_DEPLOY_FAILED = "TEMPLATE_DEPLOY_FAILED" # the deploy was undone; `details.step` is the step that failed
|
|
33
|
+
TEMPLATE_DEPLOY_NOT_FOUND = "TEMPLATE_DEPLOY_NOT_FOUND"
|
|
34
|
+
IDEMPOTENCY_KEY_CONFLICT = "IDEMPOTENCY_KEY_CONFLICT"
|
|
35
|
+
IMAGE_TEMPLATE_ONLY = "IMAGE_TEMPLATE_ONLY"
|
|
36
|
+
IMAGE_IN_USE = "IMAGE_IN_USE" # `details.templates` lists the ids of the templates that use the image
|
|
37
|
+
TEMPLATE_SAVE_NOT_ALLOWED = "TEMPLATE_SAVE_NOT_ALLOWED"
|
|
38
|
+
TEMPLATE_UPDATE_BLOCKED = "TEMPLATE_UPDATE_BLOCKED" # `VertraAPIError.update_blocked`
|
|
39
|
+
TEMPLATE_DEPRECATED = "TEMPLATE_DEPRECATED"
|
|
40
|
+
TEMPLATE_VERSION_NOT_SELECTABLE = "TEMPLATE_VERSION_NOT_SELECTABLE"
|
|
41
|
+
TEMPLATE_DETACH_CONFIRMATION_REQUIRED = "TEMPLATE_DETACH_CONFIRMATION_REQUIRED" # `VertraAPIError.detach_action`
|
|
42
|
+
TEMPLATE_ROLLBACK_REQUIRES_DATA = "TEMPLATE_ROLLBACK_REQUIRES_DATA"
|
|
43
|
+
TEMPLATE_STORAGE_UNAVAILABLE = "TEMPLATE_STORAGE_UNAVAILABLE"
|
|
44
|
+
MANAGED_ENV_READONLY = "MANAGED_ENV_READONLY"
|
|
45
|
+
|
|
46
|
+
# -- share links
|
|
47
|
+
SHARE_LINK_NOT_FOUND = "SHARE_LINK_NOT_FOUND"
|
|
48
|
+
SHARE_LINK_REVOKED = "SHARE_LINK_REVOKED"
|
|
49
|
+
SHARE_LINK_EXPIRED = "SHARE_LINK_EXPIRED"
|
|
50
|
+
SHARE_LINK_EXHAUSTED = "SHARE_LINK_EXHAUSTED"
|
|
51
|
+
|
|
52
|
+
# -- stacks
|
|
53
|
+
STACK_NOT_FOUND = "STACK_NOT_FOUND"
|
|
54
|
+
STACK_MEMBER_CONFLICT = "STACK_MEMBER_CONFLICT"
|
|
55
|
+
STACK_MEMBER_NOT_FOUND = "STACK_MEMBER_NOT_FOUND"
|
|
56
|
+
STACK_BINDING_NOT_FOUND = "STACK_BINDING_NOT_FOUND"
|
|
57
|
+
STACK_NETWORK_MODE_UNAVAILABLE = "STACK_NETWORK_MODE_UNAVAILABLE"
|
|
58
|
+
STACK_SERVICE_LOCKED = "STACK_SERVICE_LOCKED"
|
|
59
|
+
STACK_NAME_MISMATCH = "STACK_NAME_MISMATCH"
|
|
60
|
+
STACK_TRANSFORM_EMPTY = "STACK_TRANSFORM_EMPTY"
|
|
61
|
+
STACK_BLOB_PREFIX_TAKEN = "STACK_BLOB_PREFIX_TAKEN"
|
|
62
|
+
|
|
63
|
+
# -- API keys restricted to a Blob folder, or managed by the platform
|
|
64
|
+
BLOB_PREFIX_DENIED = "BLOB_PREFIX_DENIED"
|
|
65
|
+
API_KEY_MANAGED = "API_KEY_MANAGED"
|
|
@@ -45,6 +45,41 @@ class VertraAPIError(Exception):
|
|
|
45
45
|
return []
|
|
46
46
|
return [item for item in raw if isinstance(item, dict)]
|
|
47
47
|
|
|
48
|
+
def _details_for(self, code: str) -> dict[str, Any] | None:
|
|
49
|
+
if self.code != code or not isinstance(self.details, dict):
|
|
50
|
+
return None
|
|
51
|
+
return self.details
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def manifest_issues(self) -> list[dict[str, Any]]:
|
|
55
|
+
"""Findings of a `TEMPLATE_MANIFEST_INVALID` error: the objects in `details.issues` (`{path, code, message}`).
|
|
56
|
+
Tolerant like `issues`: any other error gives `[]`."""
|
|
57
|
+
details = self._details_for("TEMPLATE_MANIFEST_INVALID")
|
|
58
|
+
raw = details.get("issues") if details else None
|
|
59
|
+
return [item for item in raw if isinstance(item, dict)] if isinstance(raw, list) else []
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def secret_files(self) -> list[str]:
|
|
63
|
+
"""Paths of a `TEMPLATE_SECRET_IN_SOURCE` error: the files of the code sent to become a template that hold
|
|
64
|
+
a secret. Any other error gives `[]`."""
|
|
65
|
+
details = self._details_for("TEMPLATE_SECRET_IN_SOURCE")
|
|
66
|
+
raw = details.get("files") if details else None
|
|
67
|
+
return [item for item in raw if isinstance(item, str)] if isinstance(raw, list) else []
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def update_blocked(self) -> dict[str, Any] | None:
|
|
71
|
+
"""Reason of a `TEMPLATE_UPDATE_BLOCKED` error (`{reason, details?}`); `None` for any other error."""
|
|
72
|
+
return self._details_for("TEMPLATE_UPDATE_BLOCKED")
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def detach_action(self) -> str | None:
|
|
76
|
+
"""Action of a `TEMPLATE_DETACH_CONFIRMATION_REQUIRED` error (`files.write`, `deploy.code`,
|
|
77
|
+
`deploy.github` or `commit`): repeat the call with `confirm_detach=True` to detach the app from its template.
|
|
78
|
+
`None` for any other error."""
|
|
79
|
+
details = self._details_for("TEMPLATE_DETACH_CONFIRMATION_REQUIRED")
|
|
80
|
+
action = details.get("action") if details else None
|
|
81
|
+
return action if isinstance(action, str) else None
|
|
82
|
+
|
|
48
83
|
|
|
49
84
|
class AuthenticationError(VertraAPIError):
|
|
50
85
|
"""401 — invalid key or user not found (`API_KEY_INVALID`, `USER_NOT_FOUND`)."""
|
|
@@ -73,7 +73,8 @@ class AppDeploysWebhookResource:
|
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
def create(self, app_id: str, body: CreateWebhookBody, *, workspace_id: str | None = None) -> APIWebhookUrl:
|
|
76
|
-
"""POST /v1/apps/:id/deploys/webhook — scope `apps:write`.""
|
|
76
|
+
"""POST /v1/apps/:id/deploys/webhook — scope `apps:write`. `body["confirm_detach"]` confirms that linking
|
|
77
|
+
GitHub detaches an app of an open template from it (`TEMPLATE_DETACH_CONFIRMATION_REQUIRED` otherwise)."""
|
|
77
78
|
return self._c.request_json(
|
|
78
79
|
"POST",
|
|
79
80
|
f"/v1/apps/{encode_path_param(app_id)}/deploys/webhook",
|
|
@@ -335,9 +336,22 @@ class AppFilesResource:
|
|
|
335
336
|
content: str | None = None,
|
|
336
337
|
last_modified: str | None = None,
|
|
337
338
|
workspace_id: str | None = None,
|
|
339
|
+
confirm_detach: bool = False,
|
|
338
340
|
) -> None:
|
|
339
|
-
"""PUT /v1/apps/:id/files — scope `apps:files`. Response is void.
|
|
340
|
-
|
|
341
|
+
"""PUT /v1/apps/:id/files — scope `apps:files`. Response is void.
|
|
342
|
+
|
|
343
|
+
An app that came from an open template stops receiving versions of it when its code is changed: without
|
|
344
|
+
`confirm_detach=True` the call fails with `TEMPLATE_DETACH_CONFIRMATION_REQUIRED` and changes nothing
|
|
345
|
+
(`VertraAPIError.detach_action` says which action); with it the change is made and the app is detached.
|
|
346
|
+
Ignored for an app that does not come from a template. The same applies to `move`, `delete`, `upload`
|
|
347
|
+
and `deploys.webhook.create`."""
|
|
348
|
+
body = {
|
|
349
|
+
"path": path,
|
|
350
|
+
"content": content,
|
|
351
|
+
"last_modified": last_modified,
|
|
352
|
+
"workspace_id": workspace_id,
|
|
353
|
+
"confirm_detach": True if confirm_detach else None,
|
|
354
|
+
}
|
|
341
355
|
return self._c.request_json(
|
|
342
356
|
"PUT",
|
|
343
357
|
f"/v1/apps/{encode_path_param(app_id)}/files",
|
|
@@ -345,7 +359,8 @@ class AppFilesResource:
|
|
|
345
359
|
)
|
|
346
360
|
|
|
347
361
|
def move(self, app_id: str, body: MoveFileBody, *, workspace_id: str | None = None) -> None:
|
|
348
|
-
"""PATCH /v1/apps/:id/files — scope `apps:files`. Body: `{path, to, workspace_id?}
|
|
362
|
+
"""PATCH /v1/apps/:id/files — scope `apps:files`. Body: `{path, to, workspace_id?, confirm_detach?}`
|
|
363
|
+
(see `write` for `confirm_detach`)."""
|
|
349
364
|
merged = {**body, "workspace_id": workspace_id or body.get("workspace_id")}
|
|
350
365
|
return self._c.request_json(
|
|
351
366
|
"PATCH",
|
|
@@ -353,9 +368,10 @@ class AppFilesResource:
|
|
|
353
368
|
json_body={k: v for k, v in merged.items() if v is not None},
|
|
354
369
|
)
|
|
355
370
|
|
|
356
|
-
def delete(self, app_id: str, *, path: str, workspace_id: str | None = None) -> None:
|
|
357
|
-
"""DELETE /v1/apps/:id/files — scope `apps:files`. Body: `{path, workspace_id?}
|
|
358
|
-
|
|
371
|
+
def delete(self, app_id: str, *, path: str, workspace_id: str | None = None, confirm_detach: bool = False) -> None:
|
|
372
|
+
"""DELETE /v1/apps/:id/files — scope `apps:files`. Body: `{path, workspace_id?, confirm_detach?}`
|
|
373
|
+
(see `write` for `confirm_detach`)."""
|
|
374
|
+
body = {"path": path, "workspace_id": workspace_id, "confirm_detach": True if confirm_detach else None}
|
|
359
375
|
return self._c.request_json(
|
|
360
376
|
"DELETE",
|
|
361
377
|
f"/v1/apps/{encode_path_param(app_id)}/files",
|
|
@@ -372,6 +388,7 @@ class AppFilesResource:
|
|
|
372
388
|
path: str | None = None,
|
|
373
389
|
message: str | None = None,
|
|
374
390
|
workspace_id: str | None = None,
|
|
391
|
+
confirm_detach: bool = False,
|
|
375
392
|
timeout: float | None = None,
|
|
376
393
|
) -> APIAppFileUploadResponse:
|
|
377
394
|
"""POST /v1/apps/:id/files/upload — scope `apps:files`. Multipart, field `file`;
|
|
@@ -379,6 +396,7 @@ class AppFilesResource:
|
|
|
379
396
|
`path` is the destination folder inside the app (omitted or empty means the root);
|
|
380
397
|
a ZIP is extracted into it, any other file is written to `path/<filename>`.
|
|
381
398
|
`message` (up to 500 characters) is the commit message shown in the release history.
|
|
399
|
+
`confirm_detach=True` is sent as the query `confirm_detach=true` (see `write`).
|
|
382
400
|
Returns `app_id` and `updated_at`; ZIP uploads also include dependency metadata."""
|
|
383
401
|
fields = [MultipartField("file", file, filename=filename)]
|
|
384
402
|
if message is not None:
|
|
@@ -387,7 +405,12 @@ class AppFilesResource:
|
|
|
387
405
|
"POST",
|
|
388
406
|
f"/v1/apps/{encode_path_param(app_id)}/files/upload",
|
|
389
407
|
fields=fields,
|
|
390
|
-
query={
|
|
408
|
+
query={
|
|
409
|
+
"restart": restart,
|
|
410
|
+
"path": path,
|
|
411
|
+
"workspace_id": workspace_id,
|
|
412
|
+
"confirm_detach": True if confirm_detach else None,
|
|
413
|
+
},
|
|
391
414
|
timeout=timeout if timeout is not None else LARGE_PAYLOAD_TIMEOUT,
|
|
392
415
|
)
|
|
393
416
|
|
|
@@ -502,7 +525,9 @@ class AppsResource:
|
|
|
502
525
|
"""POST /v1/apps/image — scope `apps:write`. JSON. Creates an app that runs from a custom
|
|
503
526
|
image, which must be ready (`images.get(...)["status"] == "success"`). Replies 202 with
|
|
504
527
|
`status: "building"` and `custom_image` filled in; follow the build as with `create`.
|
|
505
|
-
`web: False` makes an app without traffic; `subdomain` and `port` need `web: True`.
|
|
528
|
+
`web: False` makes an app without traffic; `subdomain` and `port` need `web: True`. An image that
|
|
529
|
+
belongs to a template (a platform image or a `template_copy`) fails with `IMAGE_TEMPLATE_ONLY`: use the
|
|
530
|
+
template instead.
|
|
506
531
|
Doc: https://docs.vertracloud.app/api-reference/endpoint/apps/create-from-image
|
|
507
532
|
"""
|
|
508
533
|
return self._c.request_json("POST", "/v1/apps/image", json_body=body)
|
|
@@ -8,6 +8,7 @@ from ..client import encode_path_param
|
|
|
8
8
|
from ..types import (
|
|
9
9
|
APIDatabase,
|
|
10
10
|
APIDatabaseCertificate,
|
|
11
|
+
APIDatabaseCertificateReset,
|
|
11
12
|
APIDatabasePasswordReset,
|
|
12
13
|
APIDatabaseStatus,
|
|
13
14
|
APIDatabaseStatusShort,
|
|
@@ -36,8 +37,10 @@ class DatabaseCertificateResource:
|
|
|
36
37
|
timeout=timeout,
|
|
37
38
|
)
|
|
38
39
|
|
|
39
|
-
def reset(self, db_id: str, *, workspace_id: str | None = None) ->
|
|
40
|
-
"""POST /v1/databases/:id/credentials/certificate/reset — scope `databases:credentials`.
|
|
40
|
+
def reset(self, db_id: str, *, workspace_id: str | None = None) -> APIDatabaseCertificateReset:
|
|
41
|
+
"""POST /v1/databases/:id/credentials/certificate/reset — scope `databases:credentials`. Besides the new
|
|
42
|
+
certificate, the answer lists the apps of stacks connected to the database that must restart to read it
|
|
43
|
+
(`restart_required`, `apps_to_restart`)."""
|
|
41
44
|
return self._c.request_json(
|
|
42
45
|
"POST",
|
|
43
46
|
f"/v1/databases/{encode_path_param(db_id)}/credentials/certificate/reset",
|
|
@@ -50,7 +53,9 @@ class DatabasePasswordResource:
|
|
|
50
53
|
self._c = client
|
|
51
54
|
|
|
52
55
|
def reset(self, db_id: str, *, workspace_id: str | None = None) -> APIDatabasePasswordReset:
|
|
53
|
-
"""POST /v1/databases/:id/credentials/reset — scope `databases:credentials`.
|
|
56
|
+
"""POST /v1/databases/:id/credentials/reset — scope `databases:credentials`. The answer also lists the apps
|
|
57
|
+
of stacks connected to the database that must restart to read the new password (`restart_required`,
|
|
58
|
+
`apps_to_restart`)."""
|
|
54
59
|
return self._c.request_json(
|
|
55
60
|
"POST", f"/v1/databases/{encode_path_param(db_id)}/credentials/reset", query={"workspace_id": workspace_id}
|
|
56
61
|
)
|
|
@@ -7,6 +7,10 @@ is `success` or `failed`.
|
|
|
7
7
|
|
|
8
8
|
`workspace_id` is a keyword-only query parameter on the routes that act on an existing image and
|
|
9
9
|
on `list`. Without it the call acts on the caller's own images, as the apps routes do.
|
|
10
|
+
|
|
11
|
+
An image that came from a template (`kind == "template_copy"`, or a platform image) cannot be renamed, rebuilt,
|
|
12
|
+
linked to GitHub or committed to: those calls fail with `IMAGE_TEMPLATE_ONLY`. Deleting an image that is the
|
|
13
|
+
source of one of your private templates fails with `IMAGE_IN_USE`.
|
|
10
14
|
"""
|
|
11
15
|
|
|
12
16
|
from __future__ import annotations
|