Skip to main content

craftainer-crud-sdk

Python client SDK for craftainer-crud APIs, built on httpx2: sync and async clients, HTTP caching with pluggable stores, and server-sent events. Import name: craftainer_crud_sdk; requires Python 3.14+.

pip install craftainer-crud-sdk            # in-memory cache only
pip install "craftainer-crud-sdk[redis]"   # adds the Redis cache store

It is an instance of template-python-package (see docs/TEMPLATE.md for the conventions shared by every instance). The cross-module contracts live in src/craftainer_crud_sdk/interfaces.py.

Quick start

from craftainer_crud_sdk import CachePolicy, CrudClient, MemoryStore, enable_cache, static_token

with CrudClient("https://api.example.com", token_provider=static_token("<jwt>")) as client:
    # Discovery: GET /.well-known/api-catalog -> manifest, loaded on first use.
    print([r.name for r in client.manifest().resources])
    heroes = client.resource("heroes")

    # A list query, validated against the manifest (filter ops, sortable fields) before sending.
    strong = heroes.list(
        {"power_level__min": 10, "name__icontains": "a"}, sort="-power_level", limit=20
    )
    everyone = list(heroes.iter_all(sort="name"))  # pages with skip += limit

    # Opt in to caching: policies come from the manifest `cache` hints, the layer is installed as
    # a transport hook, and one SSE stream per resource invalidates it on every change.
    layer = enable_cache(
        client,
        MemoryStore(max_entries=1024),
        overrides={"heroes": CachePolicy(max_age=60)},  # fresh hits, only while the stream is up
        auto_if_match=True,  # update/delete send the cached ETag
    )
    heroes.list()  # network
    heroes.list()  # no request: fresh, and the event stream is connected
    heroes.update(1, {"name": "Grace"})  # writes through to the record entry, clears the lists
# leaving the block stops the event streams

The async client is the same with await: AsyncCrudClient, AsyncMemoryStore and await aenable_cache(client, store, ...); closing it (async with / await client.aclose()) cancels the event tasks and the layer's background revalidations.

enable_cache(client, store, *, resources=None, overrides=None, events=True, auto_if_match=False) caches every resource in the manifest unless resources names some; overrides maps a resource name to a CachePolicy that replaces its manifest default; events=False skips the SSE streams (every read then revalidates); auto_if_match=True makes update/delete without if_match send the cached record ETag, so a stale write is a PreconditionFailed (412) and evicts the record. It is a function, not a client method, because the import contract keeps client and events independent of each other. It returns the layer, whose clear, cached_etag and events_active stay available.

Caching

Caching is opt-in per resource and is a transport hook, so it works the same for the sync and async clients. A CachePolicy is defaulted from the manifest cache hints (CachePolicy.from_manifest(resource_dict); enable_cache does this for you) and any field can be overridden. (Resource.cache is the raw CacheHints of the manifest, not a policy.)

from craftainer_crud_sdk import CacheLayer, CachePolicy, MemoryStore

# Hand wiring, when `enable_cache` is not enough (`heroes` is the manifest resource dict):
layer = CacheLayer(MemoryStore(max_entries=1024), {"heroes": CachePolicy.from_manifest(heroes)})
  • By default every reuse is revalidated with If-None-Match; a 304 serves the stored body with merged headers. Only GET 200 JSON is stored, never no-store.
  • max_age > 0 (fresh hits) and stale_while_revalidate (async only) take effect only while the resource's SSE stream is connected, because events are what end freshness early (also for stats/predict, whose stale_while_revalidate needs the stream; their stats_ttl freshness does not). Wire a stream with invalidate_on_events(layer, subscriber, "heroes") (EventSubscriber / AsyncEventSubscriber). stats and predict use stats_ttl and need no stream.
  • Events clear cached prefixes; the first handshake of a stream and every reconnect clear the resource. A successful write updates or clears what it changed (PATCH writes its response through as the record entry), and a 412 evicts the record.
  • Stores: MemoryStore (LRU plus TTL) and, with the redis extra, craftainer_crud_sdk.cache.redis.RedisStore / AsyncRedisStore. A Redis outage is a cache miss and a logged warning. Cache keys carry no server identity: a store shared between servers or environments must use a per-origin namespace (for RedisStore, its namespace argument, which must not be empty).
  • filters always revalidate. The freshness gate is counted per resource, so several streams of one resource keep it open until the last is gone. enable_cache builds a policy per resource name and api_version from each manifest entry.

The design and its reasons are in docs/adrs/0002-own-thin-cache-layer-with-sse-invalidation.md.

Publishing to PyPI

Releases use PyPI trusted publishing (GitHub OIDC): no API token is stored anywhere. make publish is a no-op until you opt in, per repository:

  1. Register a "pending publisher" on the index before the first release (PyPI: Your account, Publishing, "Add a new pending publisher"; TestPyPI has its own, separate form on test.pypi.org). Enter the project name from pyproject.toml, the GitHub owner and repository, the workflow name release.yml, and leave the environment empty (or use the one release.yml runs under, if you add one).
  2. Set the repository variable PYPI_PUBLISH (Settings, Secrets and variables, Actions, Variables):
    • unset: make publish prints a skip message and the release only attaches the sdist, wheel and SBOM to the GitHub release;
    • testpypi: publish to TestPyPI;
    • pypi: publish to PyPI.
  3. Do the first release with testpypi as a dry run (a pre-release channel is enough), confirm that make release-check installs the published version back from TestPyPI, then register the PyPI pending publisher and switch the variable to pypi.

The version lives only in the git tag release.yml computes; pyproject.toml stays at 0.0.0 and make build stamps RELEASE_VERSION into the build without committing it.

License

Licensed under the GNU General Public License v3.0.

Metadata

Release files for craftainer-crud-sdk 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for craftainer-crud-sdk 0.1.0
File Size Uploaded Provenance
craftainer_crud_sdk-0.1.0.tar.gz 244.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for craftainer-crud-sdk 0.1.0
File Interpreter ABI Platform
craftainer_crud_sdk-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 307.2 kB

Release files / craftainer_crud_sdk-0.1.0.tar.gz

Download URL craftainer_crud_sdk-0.1.0.tar.gz
Size 244.4 kB
Tags Source
SHA-256 checksum
How to use checksums
10c6b91bad0899ba0dc420502661d335fcc4a0e46ae9beba3747df0948be8b5d
BLAKE2b-256 checksum
How to use checksums
ed23d794e3e546b5a568a1bc7c0146a77ef31d5e00ff53ef9169c56593096897
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / craftainer_crud_sdk-0.1.0-py3-none-any.whl

Download URL craftainer_crud_sdk-0.1.0-py3-none-any.whl
Size 62.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
da6f2dd522aadf08e0356064460dcb2888094c80bc7fefb7f2036409726e81cc
BLAKE2b-256 checksum
How to use checksums
d7e10adf5d644b34f0c2dae8a101f63343a7511a0ae0167d19724cc599a04901
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for craftainer-crud-sdk 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page