Skip to main content

Official Python client for the CairnMark file service

Project description

cairnmark-python

The official Python client for CairnMark — a self-hostable file service over S3-compatible storage with a queryable Postgres metadata layer.

Sync and async clients, streaming uploads/downloads, client-side checksum verification, typed errors, safe retries, lazy pagination. One dependency (httpx). Python ≥ 3.10, fully typed (py.typed). Requires a CairnMark server ≥ v1.1.0 (cursor pagination and 409 Retry-After).

pip install cairnmark   # not yet on PyPI — until then: pip install <path to this repo>

Quickstart

from cairnmark import CairnMark

with CairnMark("http://localhost:8080") as cm:
    # Upload with tags; idempotency_key="auto" makes retries duplicate-safe.
    f = cm.upload(
        b"hello from Python",
        filename="hello.txt",
        content_type="text/plain",
        metadata={"env": "demo"},
        idempotency_key="auto",
    )
    print("uploaded:", f.id)

    # Download — follows the presign redirect and verifies the SHA-256.
    cm.download_to_file(f.id, "hello-copy.txt")

    # Search by tag, lazily across pages.
    for file in cm.iter_files(tags={"env": "demo"}):
        print("found:", file.id, file.filename)

Async is the same surface with await (and aiter_bytes/aread/aclose on downloads):

from cairnmark import AsyncCairnMark

async with AsyncCairnMark("http://localhost:8080") as cm:
    f = await cm.upload(b"hello", filename="hello.txt")
    async with await cm.download(f.id, verify=True) as dl:
        data = await dl.aread()
    async for file in cm.iter_files(tags={"env": "demo"}):
        ...

Methods

Method Does
upload(content, *, filename, content_type, size, metadata, idempotency_key) Stream bytes / a file-like / an iterator up. idempotency_key="auto" generates a key.
upload_file(path, ...) Upload from disk; name and size inferred.
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.
download_to_file(id, path) Download to disk, checksum-verified; removes the file on failure.
presign_url(id) Mint the presigned object-store URL without following it.
get_metadata(id) Fetch the file record (a frozen File dataclass).
update_metadata(id, tags, mode="merge"|"replace") Patch tags.
delete(id) Soft-delete.
list(...) / iter_files(...) One page / lazy iteration over every match.
health() / ready() Liveness / readiness probes (raise on failure).

Configuration

CairnMark(base_url, ...) / AsyncCairnMark(base_url, ...) keyword options:

Option Default Does
headers None Default headers on every request; the hook for gateway credentials.
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.
retries 2 Retries after a network error or 5xx (so up to retries + 1 attempts). 0 disables.
user_agent cairnmark-python/<version> Override the User-Agent.
transport None Custom httpx.BaseTransport (AsyncBaseTransport for async) — proxies, mocking, UDS.

Errors

Every non-2xx response raises a subclass of APIError (which carries .status, .message, and .retry_after on 409), itself a CairnMarkError:

InvalidRequestError (400) · NotFoundError (404) · IdempotencyConflictError (409) · IdempotencyGoneError (410) · TooLargeError (413) · RangeNotSatisfiableError (416) · ServerError (5xx) — plus ChecksumMismatchError from verified download streams.

Semantics worth knowing

  • Retries. Network errors and 5xx are retried with jittered backoff (default 2 retries; retries= to change). Uploads retry only when they carry an idempotency key and the body can be rewound (bytes or a seekable file) — otherwise a retry could duplicate the file. A 409 (same key still in flight) waits out the server's Retry-After.
  • Checksum verification. The presigned object-store response carries no checksum header, so verify=True hashes the stream client-side against the stored SHA-256 (fetched from metadata) and raises ChecksumMismatchError as the last chunk is consumed. Range downloads can't be verified.
  • Timeouts. timeout= is httpx's per-operation timeout (connect, single socket read), so large streams aren't cut off mid-transfer.
  • Auth. The server has none; put it behind your gateway and inject credentials with headers= (or a custom transport=).

Development

uv sync
uv run pytest                    # unit tests (respx-mocked, no server needed)
uv run ruff check src tests && uv run pyright src

# integration: full round-trip against a live server
(cd ../CairnMark && docker compose up -d --build)
uv run pytest -m integration     # honors CAIRNMARK_BASE_URL, default localhost:8080

License

MIT © 2026 Michael Ramirez

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cairnmark-0.1.0.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cairnmark-0.1.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file cairnmark-0.1.0.tar.gz.

File metadata

  • Download URL: cairnmark-0.1.0.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for cairnmark-0.1.0.tar.gz
Algorithm Hash digest
SHA256 683a2fc45e84da6030cf5f24b4b996215332481846cf27cac1d9d4741b8f0880
MD5 c645d7ea175c58c5c9eddc1e3b3b2120
BLAKE2b-256 4d16b5874394a0eaf7941be7a6751ce49960dc5e3f893a9d64da61341ac88f39

See more details on using hashes here.

File details

Details for the file cairnmark-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: cairnmark-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for cairnmark-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d3337ef6436c096859e72f09cacc7dd9f104a2701f77a61022ff387def17829d
MD5 e821688d19ea2fa6af2445ae572109d1
BLAKE2b-256 bdea2e68e49e1b95d182c54c12d00d18378236329f96d882092c888817de555f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page