cairnmark 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.
- cairnmark-0.1.0/.github/workflows/ci.yml +40 -0
- cairnmark-0.1.0/.github/workflows/release.yml +33 -0
- cairnmark-0.1.0/.gitignore +7 -0
- cairnmark-0.1.0/LICENSE +21 -0
- cairnmark-0.1.0/PKG-INFO +137 -0
- cairnmark-0.1.0/README.md +121 -0
- cairnmark-0.1.0/pyproject.toml +43 -0
- cairnmark-0.1.0/src/cairnmark/__init__.py +57 -0
- cairnmark-0.1.0/src/cairnmark/_async_client.py +375 -0
- cairnmark-0.1.0/src/cairnmark/_client.py +368 -0
- cairnmark-0.1.0/src/cairnmark/_http.py +161 -0
- cairnmark-0.1.0/src/cairnmark/errors.py +82 -0
- cairnmark-0.1.0/src/cairnmark/models.py +68 -0
- cairnmark-0.1.0/src/cairnmark/py.typed +0 -0
- cairnmark-0.1.0/src/cairnmark/version.py +1 -0
- cairnmark-0.1.0/tests/conftest.py +26 -0
- cairnmark-0.1.0/tests/test_async.py +106 -0
- cairnmark-0.1.0/tests/test_download.py +105 -0
- cairnmark-0.1.0/tests/test_errors.py +76 -0
- cairnmark-0.1.0/tests/test_integration.py +80 -0
- cairnmark-0.1.0/tests/test_list.py +67 -0
- cairnmark-0.1.0/tests/test_metadata.py +67 -0
- cairnmark-0.1.0/tests/test_upload.py +102 -0
- cairnmark-0.1.0/uv.lock +321 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- run: uv sync
|
|
20
|
+
- run: uv run ruff check src tests
|
|
21
|
+
- run: uv run ruff format --check src tests
|
|
22
|
+
- run: uv run pyright src
|
|
23
|
+
- run: uv run pytest
|
|
24
|
+
|
|
25
|
+
integration:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
repository: mettjs/cairnmark
|
|
32
|
+
path: server
|
|
33
|
+
- name: Start the CairnMark stack
|
|
34
|
+
run: docker compose up -d --build --wait
|
|
35
|
+
working-directory: server
|
|
36
|
+
- uses: astral-sh/setup-uv@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.13"
|
|
39
|
+
- run: uv sync
|
|
40
|
+
- run: uv run pytest -m integration
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via trusted publishing (OIDC) — configure this repo as a
|
|
4
|
+
# trusted publisher on pypi.org (workflow: release.yml, environment: pypi)
|
|
5
|
+
# before the first tag; no token secrets needed.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- run: uv build
|
|
17
|
+
- uses: actions/upload-artifact@v4
|
|
18
|
+
with:
|
|
19
|
+
name: dist
|
|
20
|
+
path: dist/
|
|
21
|
+
|
|
22
|
+
publish:
|
|
23
|
+
needs: build
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
environment: pypi
|
|
26
|
+
permissions:
|
|
27
|
+
id-token: write
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/download-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
cairnmark-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Ramirez
|
|
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.
|
cairnmark-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cairnmark
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the CairnMark file service
|
|
5
|
+
Project-URL: Homepage, https://github.com/mettjs/cairnmark-python
|
|
6
|
+
Author: Michael Ramirez
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# cairnmark-python
|
|
18
|
+
|
|
19
|
+
The official Python client for [CairnMark](https://github.com/mettjs/cairnmark) —
|
|
20
|
+
a self-hostable file service over S3-compatible storage with a queryable
|
|
21
|
+
Postgres metadata layer.
|
|
22
|
+
|
|
23
|
+
Sync **and** async clients, streaming uploads/downloads, client-side checksum
|
|
24
|
+
verification, typed errors, safe retries, lazy pagination. One dependency
|
|
25
|
+
(`httpx`). Python ≥ 3.10, fully typed (`py.typed`). Requires a CairnMark
|
|
26
|
+
server ≥ v1.1.0 (cursor pagination and 409 `Retry-After`).
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
pip install cairnmark # not yet on PyPI — until then: pip install <path to this repo>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from cairnmark import CairnMark
|
|
36
|
+
|
|
37
|
+
with CairnMark("http://localhost:8080") as cm:
|
|
38
|
+
# Upload with tags; idempotency_key="auto" makes retries duplicate-safe.
|
|
39
|
+
f = cm.upload(
|
|
40
|
+
b"hello from Python",
|
|
41
|
+
filename="hello.txt",
|
|
42
|
+
content_type="text/plain",
|
|
43
|
+
metadata={"env": "demo"},
|
|
44
|
+
idempotency_key="auto",
|
|
45
|
+
)
|
|
46
|
+
print("uploaded:", f.id)
|
|
47
|
+
|
|
48
|
+
# Download — follows the presign redirect and verifies the SHA-256.
|
|
49
|
+
cm.download_to_file(f.id, "hello-copy.txt")
|
|
50
|
+
|
|
51
|
+
# Search by tag, lazily across pages.
|
|
52
|
+
for file in cm.iter_files(tags={"env": "demo"}):
|
|
53
|
+
print("found:", file.id, file.filename)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Async is the same surface with `await` (and `aiter_bytes`/`aread`/`aclose` on
|
|
57
|
+
downloads):
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from cairnmark import AsyncCairnMark
|
|
61
|
+
|
|
62
|
+
async with AsyncCairnMark("http://localhost:8080") as cm:
|
|
63
|
+
f = await cm.upload(b"hello", filename="hello.txt")
|
|
64
|
+
async with await cm.download(f.id, verify=True) as dl:
|
|
65
|
+
data = await dl.aread()
|
|
66
|
+
async for file in cm.iter_files(tags={"env": "demo"}):
|
|
67
|
+
...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Methods
|
|
71
|
+
|
|
72
|
+
| Method | Does |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `upload(content, *, filename, content_type, size, metadata, idempotency_key)` | Stream bytes / a file-like / an iterator up. `idempotency_key="auto"` generates a key. |
|
|
75
|
+
| `upload_file(path, ...)` | Upload from disk; name and size inferred. |
|
|
76
|
+
| `download(id, *, verify, offset, length)` | Open a content stream (context manager). Default follows the presign redirect; `offset`/`length` for a 206 range; `verify` for checksum checking. |
|
|
77
|
+
| `download_to_file(id, path)` | Download to disk, checksum-verified; removes the file on failure. |
|
|
78
|
+
| `presign_url(id)` | Mint the presigned object-store URL without following it. |
|
|
79
|
+
| `get_metadata(id)` | Fetch the file record (a frozen `File` dataclass). |
|
|
80
|
+
| `update_metadata(id, tags, mode="merge"\|"replace")` | Patch tags. |
|
|
81
|
+
| `delete(id)` | Soft-delete. |
|
|
82
|
+
| `list(...)` / `iter_files(...)` | One page / lazy iteration over every match. |
|
|
83
|
+
| `health()` / `ready()` | Liveness / readiness probes (raise on failure). |
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
`CairnMark(base_url, ...)` / `AsyncCairnMark(base_url, ...)` keyword options:
|
|
88
|
+
|
|
89
|
+
| Option | Default | Does |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `headers` | `None` | Default headers on every request; the hook for gateway credentials. |
|
|
92
|
+
| `timeout` | `30.0` | httpx per-operation timeout (connect / single socket read) — a `float` or an `httpx.Timeout` for fine-grained control. Doesn't cut off large streams. |
|
|
93
|
+
| `retries` | `2` | Retries after a network error or 5xx (so up to `retries + 1` attempts). `0` disables. |
|
|
94
|
+
| `user_agent` | `cairnmark-python/<version>` | Override the `User-Agent`. |
|
|
95
|
+
| `transport` | `None` | Custom `httpx.BaseTransport` (`AsyncBaseTransport` for async) — proxies, mocking, UDS. |
|
|
96
|
+
|
|
97
|
+
## Errors
|
|
98
|
+
|
|
99
|
+
Every non-2xx response raises a subclass of `APIError` (which carries
|
|
100
|
+
`.status`, `.message`, and `.retry_after` on 409), itself a `CairnMarkError`:
|
|
101
|
+
|
|
102
|
+
`InvalidRequestError` (400) · `NotFoundError` (404) ·
|
|
103
|
+
`IdempotencyConflictError` (409) · `IdempotencyGoneError` (410) ·
|
|
104
|
+
`TooLargeError` (413) · `RangeNotSatisfiableError` (416) · `ServerError` (5xx)
|
|
105
|
+
— plus `ChecksumMismatchError` from verified download streams.
|
|
106
|
+
|
|
107
|
+
## Semantics worth knowing
|
|
108
|
+
|
|
109
|
+
- **Retries.** Network errors and 5xx are retried with jittered backoff
|
|
110
|
+
(default 2 retries; `retries=` to change). Uploads retry **only** when they
|
|
111
|
+
carry an idempotency key *and* the body can be rewound (bytes or a seekable
|
|
112
|
+
file) — otherwise a retry could duplicate the file. A 409 (same key still in
|
|
113
|
+
flight) waits out the server's `Retry-After`.
|
|
114
|
+
- **Checksum verification.** The presigned object-store response carries no
|
|
115
|
+
checksum header, so `verify=True` hashes the stream client-side against the
|
|
116
|
+
stored SHA-256 (fetched from metadata) and raises `ChecksumMismatchError` as
|
|
117
|
+
the last chunk is consumed. Range downloads can't be verified.
|
|
118
|
+
- **Timeouts.** `timeout=` is httpx's per-operation timeout (connect, single
|
|
119
|
+
socket read), so large streams aren't cut off mid-transfer.
|
|
120
|
+
- **Auth.** The server has none; put it behind your gateway and inject
|
|
121
|
+
credentials with `headers=` (or a custom `transport=`).
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
uv sync
|
|
127
|
+
uv run pytest # unit tests (respx-mocked, no server needed)
|
|
128
|
+
uv run ruff check src tests && uv run pyright src
|
|
129
|
+
|
|
130
|
+
# integration: full round-trip against a live server
|
|
131
|
+
(cd ../CairnMark && docker compose up -d --build)
|
|
132
|
+
uv run pytest -m integration # honors CAIRNMARK_BASE_URL, default localhost:8080
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
[MIT](LICENSE) © 2026 Michael Ramirez
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# cairnmark-python
|
|
2
|
+
|
|
3
|
+
The official Python client for [CairnMark](https://github.com/mettjs/cairnmark) —
|
|
4
|
+
a self-hostable file service over S3-compatible storage with a queryable
|
|
5
|
+
Postgres metadata layer.
|
|
6
|
+
|
|
7
|
+
Sync **and** async clients, streaming uploads/downloads, client-side checksum
|
|
8
|
+
verification, typed errors, safe retries, lazy pagination. One dependency
|
|
9
|
+
(`httpx`). Python ≥ 3.10, fully typed (`py.typed`). Requires a CairnMark
|
|
10
|
+
server ≥ v1.1.0 (cursor pagination and 409 `Retry-After`).
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pip install cairnmark # not yet on PyPI — until then: pip install <path to this repo>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from cairnmark import CairnMark
|
|
20
|
+
|
|
21
|
+
with CairnMark("http://localhost:8080") as cm:
|
|
22
|
+
# Upload with tags; idempotency_key="auto" makes retries duplicate-safe.
|
|
23
|
+
f = cm.upload(
|
|
24
|
+
b"hello from Python",
|
|
25
|
+
filename="hello.txt",
|
|
26
|
+
content_type="text/plain",
|
|
27
|
+
metadata={"env": "demo"},
|
|
28
|
+
idempotency_key="auto",
|
|
29
|
+
)
|
|
30
|
+
print("uploaded:", f.id)
|
|
31
|
+
|
|
32
|
+
# Download — follows the presign redirect and verifies the SHA-256.
|
|
33
|
+
cm.download_to_file(f.id, "hello-copy.txt")
|
|
34
|
+
|
|
35
|
+
# Search by tag, lazily across pages.
|
|
36
|
+
for file in cm.iter_files(tags={"env": "demo"}):
|
|
37
|
+
print("found:", file.id, file.filename)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Async is the same surface with `await` (and `aiter_bytes`/`aread`/`aclose` on
|
|
41
|
+
downloads):
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from cairnmark import AsyncCairnMark
|
|
45
|
+
|
|
46
|
+
async with AsyncCairnMark("http://localhost:8080") as cm:
|
|
47
|
+
f = await cm.upload(b"hello", filename="hello.txt")
|
|
48
|
+
async with await cm.download(f.id, verify=True) as dl:
|
|
49
|
+
data = await dl.aread()
|
|
50
|
+
async for file in cm.iter_files(tags={"env": "demo"}):
|
|
51
|
+
...
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Methods
|
|
55
|
+
|
|
56
|
+
| Method | Does |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `upload(content, *, filename, content_type, size, metadata, idempotency_key)` | Stream bytes / a file-like / an iterator up. `idempotency_key="auto"` generates a key. |
|
|
59
|
+
| `upload_file(path, ...)` | Upload from disk; name and size inferred. |
|
|
60
|
+
| `download(id, *, verify, offset, length)` | Open a content stream (context manager). Default follows the presign redirect; `offset`/`length` for a 206 range; `verify` for checksum checking. |
|
|
61
|
+
| `download_to_file(id, path)` | Download to disk, checksum-verified; removes the file on failure. |
|
|
62
|
+
| `presign_url(id)` | Mint the presigned object-store URL without following it. |
|
|
63
|
+
| `get_metadata(id)` | Fetch the file record (a frozen `File` dataclass). |
|
|
64
|
+
| `update_metadata(id, tags, mode="merge"\|"replace")` | Patch tags. |
|
|
65
|
+
| `delete(id)` | Soft-delete. |
|
|
66
|
+
| `list(...)` / `iter_files(...)` | One page / lazy iteration over every match. |
|
|
67
|
+
| `health()` / `ready()` | Liveness / readiness probes (raise on failure). |
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
`CairnMark(base_url, ...)` / `AsyncCairnMark(base_url, ...)` keyword options:
|
|
72
|
+
|
|
73
|
+
| Option | Default | Does |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `headers` | `None` | Default headers on every request; the hook for gateway credentials. |
|
|
76
|
+
| `timeout` | `30.0` | httpx per-operation timeout (connect / single socket read) — a `float` or an `httpx.Timeout` for fine-grained control. Doesn't cut off large streams. |
|
|
77
|
+
| `retries` | `2` | Retries after a network error or 5xx (so up to `retries + 1` attempts). `0` disables. |
|
|
78
|
+
| `user_agent` | `cairnmark-python/<version>` | Override the `User-Agent`. |
|
|
79
|
+
| `transport` | `None` | Custom `httpx.BaseTransport` (`AsyncBaseTransport` for async) — proxies, mocking, UDS. |
|
|
80
|
+
|
|
81
|
+
## Errors
|
|
82
|
+
|
|
83
|
+
Every non-2xx response raises a subclass of `APIError` (which carries
|
|
84
|
+
`.status`, `.message`, and `.retry_after` on 409), itself a `CairnMarkError`:
|
|
85
|
+
|
|
86
|
+
`InvalidRequestError` (400) · `NotFoundError` (404) ·
|
|
87
|
+
`IdempotencyConflictError` (409) · `IdempotencyGoneError` (410) ·
|
|
88
|
+
`TooLargeError` (413) · `RangeNotSatisfiableError` (416) · `ServerError` (5xx)
|
|
89
|
+
— plus `ChecksumMismatchError` from verified download streams.
|
|
90
|
+
|
|
91
|
+
## Semantics worth knowing
|
|
92
|
+
|
|
93
|
+
- **Retries.** Network errors and 5xx are retried with jittered backoff
|
|
94
|
+
(default 2 retries; `retries=` to change). Uploads retry **only** when they
|
|
95
|
+
carry an idempotency key *and* the body can be rewound (bytes or a seekable
|
|
96
|
+
file) — otherwise a retry could duplicate the file. A 409 (same key still in
|
|
97
|
+
flight) waits out the server's `Retry-After`.
|
|
98
|
+
- **Checksum verification.** The presigned object-store response carries no
|
|
99
|
+
checksum header, so `verify=True` hashes the stream client-side against the
|
|
100
|
+
stored SHA-256 (fetched from metadata) and raises `ChecksumMismatchError` as
|
|
101
|
+
the last chunk is consumed. Range downloads can't be verified.
|
|
102
|
+
- **Timeouts.** `timeout=` is httpx's per-operation timeout (connect, single
|
|
103
|
+
socket read), so large streams aren't cut off mid-transfer.
|
|
104
|
+
- **Auth.** The server has none; put it behind your gateway and inject
|
|
105
|
+
credentials with `headers=` (or a custom `transport=`).
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
uv sync
|
|
111
|
+
uv run pytest # unit tests (respx-mocked, no server needed)
|
|
112
|
+
uv run ruff check src tests && uv run pyright src
|
|
113
|
+
|
|
114
|
+
# integration: full round-trip against a live server
|
|
115
|
+
(cd ../CairnMark && docker compose up -d --build)
|
|
116
|
+
uv run pytest -m integration # honors CAIRNMARK_BASE_URL, default localhost:8080
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
[MIT](LICENSE) © 2026 Michael Ramirez
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cairnmark"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python client for the CairnMark file service"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Michael Ramirez" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["httpx>=0.27"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/mettjs/cairnmark-python"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=8",
|
|
27
|
+
"pytest-asyncio>=0.24",
|
|
28
|
+
"respx>=0.21",
|
|
29
|
+
"ruff>=0.6",
|
|
30
|
+
"pyright>=1.1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/cairnmark"]
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
line-length = 100
|
|
38
|
+
target-version = "py310"
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
markers = ["integration: requires a live CairnMark server"]
|
|
42
|
+
addopts = "-m 'not integration'"
|
|
43
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Official Python client for the CairnMark file service.
|
|
2
|
+
|
|
3
|
+
Synchronous::
|
|
4
|
+
|
|
5
|
+
from cairnmark import CairnMark
|
|
6
|
+
|
|
7
|
+
with CairnMark("http://localhost:8080") as cm:
|
|
8
|
+
f = cm.upload(b"hello", filename="hello.txt", metadata={"env": "demo"},
|
|
9
|
+
idempotency_key="auto")
|
|
10
|
+
cm.download_to_file(f.id, "hello-copy.txt") # checksum-verified
|
|
11
|
+
|
|
12
|
+
Asynchronous::
|
|
13
|
+
|
|
14
|
+
from cairnmark import AsyncCairnMark
|
|
15
|
+
|
|
16
|
+
async with AsyncCairnMark("http://localhost:8080") as cm:
|
|
17
|
+
f = await cm.upload(b"hello", filename="hello.txt")
|
|
18
|
+
async for file in cm.iter_files(tags={"env": "demo"}):
|
|
19
|
+
...
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from ._async_client import AsyncCairnMark, AsyncDownload
|
|
23
|
+
from ._client import CairnMark, Download
|
|
24
|
+
from .errors import (
|
|
25
|
+
APIError,
|
|
26
|
+
CairnMarkError,
|
|
27
|
+
ChecksumMismatchError,
|
|
28
|
+
IdempotencyConflictError,
|
|
29
|
+
IdempotencyGoneError,
|
|
30
|
+
InvalidRequestError,
|
|
31
|
+
NotFoundError,
|
|
32
|
+
RangeNotSatisfiableError,
|
|
33
|
+
ServerError,
|
|
34
|
+
TooLargeError,
|
|
35
|
+
)
|
|
36
|
+
from .models import File, ListPage
|
|
37
|
+
from .version import __version__
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"APIError",
|
|
41
|
+
"AsyncCairnMark",
|
|
42
|
+
"AsyncDownload",
|
|
43
|
+
"CairnMark",
|
|
44
|
+
"CairnMarkError",
|
|
45
|
+
"ChecksumMismatchError",
|
|
46
|
+
"Download",
|
|
47
|
+
"File",
|
|
48
|
+
"IdempotencyConflictError",
|
|
49
|
+
"IdempotencyGoneError",
|
|
50
|
+
"InvalidRequestError",
|
|
51
|
+
"ListPage",
|
|
52
|
+
"NotFoundError",
|
|
53
|
+
"RangeNotSatisfiableError",
|
|
54
|
+
"ServerError",
|
|
55
|
+
"TooLargeError",
|
|
56
|
+
"__version__",
|
|
57
|
+
]
|