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; a304serves the stored body with merged headers. OnlyGET200JSON is stored, neverno-store. max_age > 0(fresh hits) andstale_while_revalidate(async only) take effect only while the resource's SSE stream is connected, because events are what end freshness early (also forstats/predict, whosestale_while_revalidateneeds the stream; theirstats_ttlfreshness does not). Wire a stream withinvalidate_on_events(layer, subscriber, "heroes")(EventSubscriber/AsyncEventSubscriber).statsandpredictusestats_ttland 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 (
PATCHwrites its response through as the record entry), and a412evicts the record. - Stores:
MemoryStore(LRU plus TTL) and, with theredisextra,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 (forRedisStore, itsnamespaceargument, which must not be empty). filtersalways revalidate. The freshness gate is counted per resource, so several streams of one resource keep it open until the last is gone.enable_cachebuilds a policy per resource name andapi_versionfrom 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:
- 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 namerelease.yml, and leave the environment empty (or use the onerelease.ymlruns under, if you add one). - Set the repository variable
PYPI_PUBLISH(Settings, Secrets and variables, Actions, Variables):- unset:
make publishprints 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.
- unset:
- Do the first release with
testpypias a dry run (a pre-release channel is enough), confirm thatmake release-checkinstalls the published version back from TestPyPI, then register the PyPI pending publisher and switch the variable topypi.
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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| craftainer_crud_sdk-0.1.0.tar.gz | 244.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.