cavs — Python SDK for CAVS Hub
Official Python client for CAVS Hub, the
content-addressed, deduplicating artifact store for AI, CI/CD and build pipelines.
It speaks the same canonical API the CLI uses — one API, one permission model, one
data plane. The wire behaviour is defined in ../CONTRACT.md and
pinned by the shared vectors in ../spec/contract/.
- Requires Python 3.9+.
- Only runtime dependency:
httpx.
Install
pip install cavs
From this monorepo checkout:
cd python
pip install -e ".[dev]"
Quickstart
from cavs import CAVS
client = CAVS.from_env() # reads CAVS_TOKEN + CAVS_API
artifact = client.artifacts.upload(
path="./model",
project="vision-models",
name="resnet50",
kind="model",
version="1.4.0",
metadata={"framework": "pytorch", "accuracy": 0.942},
)
print(artifact.reference) # cavs://acme-ai/vision-models/model/resnet50:1.4.0
print(artifact.deduplication_ratio)
client.artifacts.download(artifact.reference, dest="./restored")
Track training runs and lineage:
run = client.runs.create(project="vision-models", name="nightly", system="mlflow")
client.runs.update("vision-models", run.id, status="completed")
Authentication
Create a service account in the CAVS dashboard and export its key. Tokens are
resolved in this order: an explicit token= argument, then $CAVS_TOKEN.
export CAVS_TOKEN=cavs_sk_...
export CAVS_API=https://cavsnode.com # default; use http://localhost:8080 for dev
Other environment variables:
| Variable | Purpose |
|---|---|
CAVS_TOKEN |
Auth token (cavs_sk_…, cavs_pat_…, …) |
CAVS_API |
Base URL (default https://cavsnode.com) |
CAVS_ORG |
Default organization slug |
CAVS_CA_BUNDLE |
Path to a custom CA bundle for TLS verification |
Or construct explicitly:
client = CAVS(token="cavs_sk_...", api="https://cavsnode.com", org="acme-ai")
project and org resolution
project is an alias for a repository. It is resolved within an org by
matching the value against each repository's id, slug or name
(GET /organizations/{org}/repositories); if nothing matches, the value is used
verbatim as a repository id. The org comes from an explicit argument, then
CAVS_ORG, then the caller's default org from GET /users/me.
Security
Following ../CONTRACT.md §2, this SDK:
- never prints the token in
repr, exceptions, or logs — it is redacted to e.g.cavs_sk_…abcd; - never logs presigned URLs or full request headers;
- verifies TLS and honours
CAVS_CA_BUNDLE; caps redirects at 5; - always sets connect + read timeouts;
- verifies
sha256 == oidon every download and refuses to write a mismatched file (ChecksumError), cleaning up the temp file; - guards against path traversal when expanding directories / archive members;
- honours
Retry-Afterand cleans up temp files on failure; - supports cancellation via a
threading.Event(or any object withis_set()).
Uploads and downloads
Large files never travel through the control plane — they stream directly to object storage via presigned URLs (CONTRACT §5).
- Upload: hash every file (streamed sha256, 64 KiB chunks) → open a session
with an
Idempotency-Key→ authorize objects (the Hub reports which alreadyexists, so dedup and resume are automatic) →PUTthe missing bytes →completeeach object →finalize(idempotent), which returns dedup stats. - Download: authorize → stream to a deterministic temp file while hashing →
verify
sha256 == oid→ atomically rename into place.
Progress and cancellation:
import threading
cancel = threading.Event()
client.artifacts.upload(
path="./big-dataset",
project="datasets",
name="imagenet",
kind="dataset",
version="2026.07.24",
on_progress=lambda done, total: print(f"{done}/{total}"),
cancel=cancel,
)
cavs:// URIs
from cavs import parse, format_uri
p = parse("cavs://acme-ai/vision-models/model/resnet50:1.4.0")
p.org, p.name, p.version # 'acme-ai', 'resnet50', '1.4.0'
format_uri(p) # round-trips to the canonical form
Errors
All exceptions derive from cavs.CAVSError. The HTTP → exception mapping follows
CONTRACT §8:
| HTTP | Exception |
|---|---|
| 401 | AuthenticationError |
| 403 | AuthorizationError |
| 404 | NotFoundError |
| 409 | ConflictError |
| 413 / 402 | QuotaExceededError |
| 429 | RateLimitError (honours Retry-After) |
| 5xx / network | TemporaryServiceError (retried, ≤4 attempts) |
| client-side checksum mismatch | ChecksumError |
| upload / download transport | UploadError / DownloadError |
from cavs import CAVS, NotFoundError, RateLimitError
try:
client.artifacts.get("does-not-exist")
except NotFoundError as e:
print(e.code, e.status)
except RateLimitError as e:
print("retry after", e.retry_after)
Development
cd python
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
ruff check . && mypy src && pytest --cov=cavs --cov-fail-under=90
Tests mock httpx with respx — no network is
used. See ../CONTRIBUTING.md and the docs in
../docs/.
License
Apache 2.0 — see ../LICENSE.
Metadata
Release files for cavs 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cavs-0.2.0.tar.gz | 26.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cavs-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.2 kB
Release files / cavs-0.2.0.tar.gz
| Download URL | cavs-0.2.0.tar.gz |
|---|---|
| Size | 26.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f00572805f61caf8d82710cdaa934c8c9b4b87d6fc56d4326db60d0f906f8048
|
|
BLAKE2b-256 checksum How to use checksums |
1b5cc218f2500b990157a0748875c0568b71a9a503acabbcffb1639ffe367c08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / cavs-0.2.0-py3-none-any.whl
| Download URL | cavs-0.2.0-py3-none-any.whl |
|---|---|
| Size | 25.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6d770394cc2163379a3cdeafc9b99a6a887cd2796818fd77093be6e986bab84e
|
|
BLAKE2b-256 checksum How to use checksums |
0e2633aac92a19fa615f3a4c72f3c9c8f16cbc324ffd5524ecc23135a0d83ec8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|