vautra-sdk 1.0.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.
- vautra_sdk-1.0.0/CHANGELOG.md +89 -0
- vautra_sdk-1.0.0/LICENSE +21 -0
- vautra_sdk-1.0.0/MANIFEST.in +10 -0
- vautra_sdk-1.0.0/PKG-INFO +348 -0
- vautra_sdk-1.0.0/README.md +307 -0
- vautra_sdk-1.0.0/pyproject.toml +92 -0
- vautra_sdk-1.0.0/setup.cfg +4 -0
- vautra_sdk-1.0.0/src/vautra/__init__.py +43 -0
- vautra_sdk-1.0.0/src/vautra/_http.py +411 -0
- vautra_sdk-1.0.0/src/vautra/_signer.py +101 -0
- vautra_sdk-1.0.0/src/vautra/_version.py +1 -0
- vautra_sdk-1.0.0/src/vautra/client.py +183 -0
- vautra_sdk-1.0.0/src/vautra/errors.py +116 -0
- vautra_sdk-1.0.0/src/vautra/py.typed +0 -0
- vautra_sdk-1.0.0/src/vautra/resources/__init__.py +13 -0
- vautra_sdk-1.0.0/src/vautra/resources/_params.py +43 -0
- vautra_sdk-1.0.0/src/vautra/resources/buckets.py +100 -0
- vautra_sdk-1.0.0/src/vautra/resources/objects.py +320 -0
- vautra_sdk-1.0.0/src/vautra/resources/projects.py +62 -0
- vautra_sdk-1.0.0/src/vautra/types.py +231 -0
- vautra_sdk-1.0.0/src/vautra/uploads/__init__.py +6 -0
- vautra_sdk-1.0.0/src/vautra/uploads/body.py +229 -0
- vautra_sdk-1.0.0/src/vautra/uploads/multipart.py +375 -0
- vautra_sdk-1.0.0/src/vautra/uploads/part_queue.py +136 -0
- vautra_sdk-1.0.0/src/vautra_sdk.egg-info/PKG-INFO +348 -0
- vautra_sdk-1.0.0/src/vautra_sdk.egg-info/SOURCES.txt +33 -0
- vautra_sdk-1.0.0/src/vautra_sdk.egg-info/dependency_links.txt +1 -0
- vautra_sdk-1.0.0/src/vautra_sdk.egg-info/requires.txt +10 -0
- vautra_sdk-1.0.0/src/vautra_sdk.egg-info/top_level.txt +1 -0
- vautra_sdk-1.0.0/tests/__init__.py +0 -0
- vautra_sdk-1.0.0/tests/conftest.py +42 -0
- vautra_sdk-1.0.0/tests/mock_server.py +457 -0
- vautra_sdk-1.0.0/tests/test_client.py +251 -0
- vautra_sdk-1.0.0/tests/test_download.py +93 -0
- vautra_sdk-1.0.0/tests/test_upload.py +359 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. This project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## 1.0.0
|
|
7
|
+
|
|
8
|
+
First published release. The package is distributed on PyPI as `vautra-sdk` and
|
|
9
|
+
imported as `vautra`.
|
|
10
|
+
|
|
11
|
+
The pre-release layout installed the top-level modules `client`, `models`,
|
|
12
|
+
`resources`, `uploads` and `vautra` into site-packages, which would have
|
|
13
|
+
collided with unrelated packages on any machine that installed it. Everything
|
|
14
|
+
now lives under a single `vautra` package.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- Redirects are never followed. Previously `requests` followed them by default,
|
|
19
|
+
which replayed signed credential headers to whatever host a `Location` header
|
|
20
|
+
named. An unexpected `3xx` now raises `VautraError` with code
|
|
21
|
+
`unexpected_redirect`.
|
|
22
|
+
- `api_url` must use `https://` unless it points at a loopback address, so
|
|
23
|
+
signed credentials cannot be sent over plaintext. Malformed URLs, wrong types,
|
|
24
|
+
and empty credentials raise `VautraConfigError` before any request is sent.
|
|
25
|
+
- `Vautra`, `VautraConfig` and the transport redact the secret access key in
|
|
26
|
+
`repr()`, so clients can be logged safely.
|
|
27
|
+
- The connection adapter is pinned to zero transport-level retries; a urllib3
|
|
28
|
+
replay would have reused a nonce, which the service rejects.
|
|
29
|
+
- Requests are signed against the URL `requests` actually puts on the wire, so
|
|
30
|
+
escaping differences cannot silently invalidate a signature.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Requests now default to a 30 second timeout. Without one, an unresponsive
|
|
35
|
+
server hung the caller forever.
|
|
36
|
+
- `objects.delete()` and `objects.cancel_upload()` return the service message.
|
|
37
|
+
Responses that carry only a message and no `data` key were being rewrapped,
|
|
38
|
+
which discarded it.
|
|
39
|
+
- Boolean query parameters are sent as `true`/`false` rather than Python's
|
|
40
|
+
`True`/`False`.
|
|
41
|
+
- The server's part plan is validated against the body size. An inconsistent
|
|
42
|
+
plan previously uploaded a truncated object and still reported success.
|
|
43
|
+
- Object keys are validated locally before an upload session is created.
|
|
44
|
+
- Non-seekable streams are spooled to a temporary file instead of being read
|
|
45
|
+
entirely into memory, so memory stays flat and failed parts stay retryable.
|
|
46
|
+
- Locally raised errors (unreadable body, bad plan, cancellation) are marked
|
|
47
|
+
non-retryable instead of consuming the retry budget.
|
|
48
|
+
- A failing part now stops its sibling workers instead of letting them run on.
|
|
49
|
+
- Progress counters are updated under a lock and count each part once, so a
|
|
50
|
+
retried part cannot push `loadedBytes` past the total.
|
|
51
|
+
- `mimeType` is omitted when the caller does not pass `content_type`, letting
|
|
52
|
+
Vautra derive it from the extension instead of storing
|
|
53
|
+
`application/octet-stream`.
|
|
54
|
+
- Buffered downloads are capped at 256 MiB (`max_bytes=0` disables the cap),
|
|
55
|
+
enforced against both `Content-Length` and the bytes actually received.
|
|
56
|
+
- `download(destination=...)` writes to a temporary sibling and renames it into
|
|
57
|
+
place, so an interrupted download leaves no truncated file.
|
|
58
|
+
- Path segments are percent-encoded and validated on every method.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- The API is snake_case throughout; the camelCase parameter and method aliases
|
|
63
|
+
are gone.
|
|
64
|
+
- Timeouts are expressed in seconds (`timeout`, `attempt_timeout`) rather than a
|
|
65
|
+
mix of seconds and milliseconds.
|
|
66
|
+
- Cancellation is expressed with a `threading.Event` passed as `cancel_event`.
|
|
67
|
+
- `download_stream()` returns a `DownloadStream` context manager that iterates
|
|
68
|
+
chunks, rather than a raw `requests.Response`.
|
|
69
|
+
- Credentials and `api_url` fall back to `VAUTRA_ACCESS_KEY_ID`,
|
|
70
|
+
`VAUTRA_SECRET_ACCESS_KEY` and `VAUTRA_API_URL`.
|
|
71
|
+
- `Vautra` is a context manager and exposes `close()`.
|
|
72
|
+
- Error types are specific: `VautraConfigError`, `VautraTimeoutError`,
|
|
73
|
+
`VautraConnectionError`, `VautraCancelledError`, all deriving from
|
|
74
|
+
`VautraError`.
|
|
75
|
+
|
|
76
|
+
### Removed
|
|
77
|
+
|
|
78
|
+
- `objects.get()` and `objects.public_link()`. Neither route exists on the
|
|
79
|
+
Vautra API, so both could only ever return `404`.
|
|
80
|
+
- `client.access_keys`, whose single method raised unconditionally. Access keys
|
|
81
|
+
are created in the dashboard, where the secret can be shown once.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- `py.typed`: the package ships inline type information.
|
|
86
|
+
- A test suite that runs against a mock Vautra API porting the backend's
|
|
87
|
+
signature verifier.
|
|
88
|
+
- CI running the tests on Python 3.9 through 3.13, plus `ruff` and strict
|
|
89
|
+
`mypy`.
|
vautra_sdk-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vautra Technologies Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vautra-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Vautra projects, buckets and VS3 object storage.
|
|
5
|
+
Author: Vautra Technologies Inc.
|
|
6
|
+
Maintainer: Vautra Technologies Inc.
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/KWP-inc/Vautra-python-sdk
|
|
9
|
+
Project-URL: Documentation, https://core-docs.vautra.com
|
|
10
|
+
Project-URL: Repository, https://github.com/KWP-inc/Vautra-python-sdk
|
|
11
|
+
Project-URL: Issues, https://github.com/KWP-inc/Vautra-python-sdk/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/KWP-inc/Vautra-python-sdk/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: vautra,vs3,object-storage,storage,sdk,upload
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: System :: Archiving
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: requests>=2.31.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
37
|
+
Requires-Dist: types-requests>=2.31; extra == "dev"
|
|
38
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
39
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# Vautra Python SDK
|
|
43
|
+
|
|
44
|
+
Python SDK for Vautra projects, buckets, and object storage.
|
|
45
|
+
|
|
46
|
+
The SDK signs every request and sends it to `https://app.vautra.com/api` by default. Keep the secret access key on a trusted server; it is a credential, not a public identifier.
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Python 3.9 or newer.
|
|
51
|
+
- `requests` 2.31 or newer (installed automatically).
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install vautra-sdk
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The distribution is `vautra-sdk`; the import name is `vautra`. The npm package of
|
|
60
|
+
the same name is the separate Node.js SDK.
|
|
61
|
+
|
|
62
|
+
## Authentication
|
|
63
|
+
|
|
64
|
+
Create an access key in the Vautra dashboard, then build one client and reuse it — each client owns a connection pool:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from vautra import Vautra
|
|
68
|
+
|
|
69
|
+
vautra = Vautra(
|
|
70
|
+
access_key_id="...",
|
|
71
|
+
secret_access_key="...",
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Credentials fall back to the `VAUTRA_ACCESS_KEY_ID` and `VAUTRA_SECRET_ACCESS_KEY` environment variables, and `api_url` to `VAUTRA_API_URL`, when the corresponding argument is omitted:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from vautra import Vautra
|
|
79
|
+
|
|
80
|
+
with Vautra() as vautra: # reads the environment
|
|
81
|
+
page = vautra.projects.list()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Every request carries a fresh nonce, including retries, so signed headers are never replayed. Access-key permissions and project or bucket restrictions are enforced by the Vautra backend.
|
|
85
|
+
|
|
86
|
+
Invalid configuration raises `VautraConfigError` immediately, before any request is sent. `api_url` must use `https://` unless it points at a loopback address, so signed credentials never travel over plaintext.
|
|
87
|
+
|
|
88
|
+
### Client options
|
|
89
|
+
|
|
90
|
+
| Option | Default | Description |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `access_key_id` | `$VAUTRA_ACCESS_KEY_ID` | Required. |
|
|
93
|
+
| `secret_access_key` | `$VAUTRA_SECRET_ACCESS_KEY` | Required. Never logged; the client redacts it in `repr()`. |
|
|
94
|
+
| `api_url` | `$VAUTRA_API_URL` or `https://app.vautra.com/api` | Must be `https://` for remote hosts. |
|
|
95
|
+
| `timeout` | `30.0` | Seconds, for non-upload requests. Use `0` or `None` to disable. |
|
|
96
|
+
| `session` | new session | Supply your own `requests.Session` to control proxies or TLS verification. |
|
|
97
|
+
|
|
98
|
+
Every method also accepts a per-call `timeout=` override and a `cancel_event=` (a `threading.Event`).
|
|
99
|
+
|
|
100
|
+
`Vautra` is a context manager, and `close()` releases the connection pool:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
with Vautra(access_key_id="...", secret_access_key="...") as vautra:
|
|
104
|
+
...
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Object keys
|
|
108
|
+
|
|
109
|
+
Vautra object keys are **flat file names**, not S3-style paths:
|
|
110
|
+
|
|
111
|
+
- No `/` or `\` separators — `documents/report.pdf` is rejected.
|
|
112
|
+
- Maximum 255 bytes (UTF-8), no control characters, no trailing period.
|
|
113
|
+
- The key must end in a supported extension.
|
|
114
|
+
|
|
115
|
+
Supported extensions are currently: `txt`, `csv`, `json`, `xml`, `html`, `css`, `js`, `ts`, `md`, `pdf`, `png`, `jpg`, `jpeg`, `gif`, `webp`, `svg`, `zip`, `gz`, `doc`, `docx`, `xlsx`, `pptx`, `mp3`, `mp4`, `webm`, `wav`, `ogg`. Files whose contents do not match their extension are rejected on upload.
|
|
116
|
+
|
|
117
|
+
Invalid keys raise `VautraError` immediately, before an upload session is created.
|
|
118
|
+
|
|
119
|
+
## Projects
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
page = vautra.projects.list(
|
|
123
|
+
page=1,
|
|
124
|
+
page_size=20,
|
|
125
|
+
search="production",
|
|
126
|
+
key_management="managed",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
project = vautra.projects.get("project-id")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
List methods return `{"data": [...], "meta": {"page", "pageSize", "total", "totalPages"}}`.
|
|
133
|
+
|
|
134
|
+
## Buckets
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
page = vautra.buckets.list(project_id="project-id", page=1, page_size=20)
|
|
138
|
+
|
|
139
|
+
bucket = vautra.buckets.get("bucket-id")
|
|
140
|
+
|
|
141
|
+
created = vautra.buckets.create(
|
|
142
|
+
project_id="project-id",
|
|
143
|
+
name="documents",
|
|
144
|
+
versioning_enabled=True,
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Bucket deletion is intentionally not exposed by the SDK.
|
|
149
|
+
|
|
150
|
+
## List objects
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
page = vautra.objects.list("bucket-id", page=1, page_size=20, search="invoice")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Upload objects
|
|
157
|
+
|
|
158
|
+
For large local files, pass a path. The SDK reads only the active chunks instead of loading the whole file into memory:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
obj = vautra.objects.upload(
|
|
162
|
+
bucket_id="bucket-id",
|
|
163
|
+
key="movie.mp4",
|
|
164
|
+
body="C:/videos/movie.mp4", # str, pathlib.Path, or {"path": ...}
|
|
165
|
+
content_type="video/mp4",
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
For content already in memory:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
vautra.objects.upload(
|
|
173
|
+
bucket_id="bucket-id",
|
|
174
|
+
key="hello.txt",
|
|
175
|
+
body=b"Hello from Vautra",
|
|
176
|
+
content_type="text/plain",
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
When `content_type` is omitted, Vautra derives the stored MIME type from the key's extension.
|
|
181
|
+
|
|
182
|
+
### Supported body types
|
|
183
|
+
|
|
184
|
+
| Body | Behaviour |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| `bytes`, `bytearray`, `memoryview` | Used directly. |
|
|
187
|
+
| `str` | Encoded as UTF-8. |
|
|
188
|
+
| `pathlib.Path`, `os.PathLike`, `{"path": "..."}` | Opened and read by range; memory stays flat. |
|
|
189
|
+
| A seekable binary file object | Read by range from its current position. The SDK does not close a handle it did not open. |
|
|
190
|
+
| A non-seekable stream (pipe, socket, generator of `bytes`) | Spooled to a temporary file so failed parts can be retried, then cleaned up. |
|
|
191
|
+
| Any object with `size` and `read(start, end)` | Used as a random-access source. |
|
|
192
|
+
|
|
193
|
+
A stream cannot be rewound, so a failed part could not otherwise be retried. Spooling keeps memory flat regardless of stream size. Prefer a path when the data is already on disk.
|
|
194
|
+
|
|
195
|
+
Text-mode file objects are rejected — open files with `"rb"`.
|
|
196
|
+
|
|
197
|
+
### Upload options
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
import threading
|
|
201
|
+
|
|
202
|
+
cancel = threading.Event()
|
|
203
|
+
|
|
204
|
+
vautra.objects.upload(
|
|
205
|
+
bucket_id="bucket-id",
|
|
206
|
+
key="archive.zip",
|
|
207
|
+
body="./archive.zip",
|
|
208
|
+
concurrency=2, # 1-4
|
|
209
|
+
max_attempts=3, # 1-5
|
|
210
|
+
attempt_timeout=180.0, # seconds per request
|
|
211
|
+
cancel_event=cancel,
|
|
212
|
+
on_started=lambda upload_id: print("session", upload_id),
|
|
213
|
+
on_progress=lambda p: print(p["stage"], p["loadedBytes"], p["totalBytes"]),
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Progress stages are `starting`, `uploading`, `finalizing`, and `success`. `loaded_bytes` never goes backwards and a retried part is counted once.
|
|
218
|
+
|
|
219
|
+
Part and completion requests retry network failures, HTTP `408`, `429`, and `5xx`. Retry delays are one second then three seconds. Other `4xx` responses are terminal, as are errors raised locally by the SDK.
|
|
220
|
+
|
|
221
|
+
If an upload fails after its session is created, the SDK makes a best-effort request to cancel and clean up that session. Secure cross-process resume is not supported.
|
|
222
|
+
|
|
223
|
+
SDK access keys work only with Vautra-managed projects and their buckets. Customer-managed projects are intentionally excluded from SDK access-key scopes.
|
|
224
|
+
|
|
225
|
+
## Upload status and cancellation
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
status = vautra.objects.upload_status("upload-id")
|
|
229
|
+
vautra.objects.cancel_upload("upload-id")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`upload_status()` is for inspection only; it does not enable resume.
|
|
233
|
+
|
|
234
|
+
To cancel a running upload, set the `threading.Event` you passed as `cancel_event`:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
import threading
|
|
238
|
+
|
|
239
|
+
cancel = threading.Event()
|
|
240
|
+
worker = threading.Thread(
|
|
241
|
+
target=vautra.objects.upload,
|
|
242
|
+
kwargs={
|
|
243
|
+
"bucket_id": "bucket-id",
|
|
244
|
+
"key": "large.zip",
|
|
245
|
+
"body": "./large.zip",
|
|
246
|
+
"cancel_event": cancel,
|
|
247
|
+
},
|
|
248
|
+
)
|
|
249
|
+
worker.start()
|
|
250
|
+
cancel.set()
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The upload raises `VautraCancelledError`, and the session is cleaned up. An already-set event raises before any request is sent.
|
|
254
|
+
|
|
255
|
+
Because `requests` is synchronous, cancellation takes effect between parts and between retry attempts — it does not interrupt a request already on the wire. `attempt_timeout` bounds that window.
|
|
256
|
+
|
|
257
|
+
## Download objects
|
|
258
|
+
|
|
259
|
+
Return the object as `bytes`:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
data = vautra.objects.download("object-id")
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Buffered downloads are capped at 256 MiB so a large object cannot exhaust memory. Pass `max_bytes` to change the cap, or `0` to disable it. Objects above the cap raise `VautraError` with status `413`.
|
|
266
|
+
|
|
267
|
+
Write directly to a file, which streams and is not capped:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
vautra.objects.download("object-id", destination="./report.pdf")
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The file is written to a temporary sibling and renamed into place, so an interrupted download never leaves a truncated file at `destination`.
|
|
274
|
+
|
|
275
|
+
Or consume the stream yourself:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
with vautra.objects.download_stream("object-id") as stream:
|
|
279
|
+
print(stream.content_type, stream.content_length)
|
|
280
|
+
for chunk in stream:
|
|
281
|
+
sink.write(chunk)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Downloads through the SDK are available only for Vautra-managed projects.
|
|
285
|
+
|
|
286
|
+
## Delete objects
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
vautra.objects.delete("object-id")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Deletion is subject to access-key permissions and backend object-state rules.
|
|
293
|
+
|
|
294
|
+
## Errors
|
|
295
|
+
|
|
296
|
+
Every SDK error derives from `VautraError`:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from vautra import VautraError
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
vautra.buckets.get("missing-bucket")
|
|
303
|
+
except VautraError as error:
|
|
304
|
+
print(error.status, error.code, error.message)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
| Exception | Raised when |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| `VautraError` | The API returned a failure. Carries `status`, `code`, `details`. |
|
|
310
|
+
| `VautraConfigError` | Client configuration is unusable. Raised before any request. |
|
|
311
|
+
| `VautraTimeoutError` | A request exceeded its time budget (`status` 408). |
|
|
312
|
+
| `VautraConnectionError` | The API could not be reached (`status` 0). |
|
|
313
|
+
| `VautraCancelledError` | The caller's `cancel_event` was set. |
|
|
314
|
+
|
|
315
|
+
Errors also expose `retryable`, which the upload retry loop uses to avoid burning attempts on failures that would repeat identically.
|
|
316
|
+
|
|
317
|
+
The SDK never follows redirects: signed credentials are not forwarded to another host, and an unexpected `3xx` surfaces as a `VautraError` with code `unexpected_redirect`.
|
|
318
|
+
|
|
319
|
+
## Thread safety
|
|
320
|
+
|
|
321
|
+
A `Vautra` client is safe to share across threads. Uploads use a bounded worker pool internally and read bodies under a lock.
|
|
322
|
+
|
|
323
|
+
## Development
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
python -m venv .venv && .venv/Scripts/activate # or source .venv/bin/activate
|
|
327
|
+
pip install -e ".[dev]"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
pytest
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
ruff check . && mypy
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The test suite runs against a mock Vautra API whose signature verification is a port of the backend's verifier, so a passing test means the SDK interoperates with the real service rather than with a permissive stub.
|
|
339
|
+
|
|
340
|
+
To try a real upload:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
python examples/upload_file.py ./examples/sample.txt --bucket-id BUCKET --key sample.txt
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
MIT. See [LICENSE](LICENSE).
|