warmhub
The Python SDK for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.
Install
pip install warmhub
Requires Python 3.10 or later. It pulls one runtime dependency, httpx, plus
typing-extensions below Python 3.13.
Add the re2 extra only if you validate shape pattern constraints in the
client. It ships no wheel for Alpine and builds from source there:
pip install "warmhub[re2]"
Versions
Every release carries a real version, assigned by the release pipeline and
never edited by hand — so importlib.metadata.version("warmhub") and a
pip freeze line identify exactly which build you have. That number is
independent of the TypeScript SDK's: a Python-only fix does not wait on a
TypeScript release, and the two are not expected to match.
An installed copy reporting 0.0.0.dev0 is not a release. That is the
placeholder the source tree carries, never published under that number, and it
is how you tell a real install apart from a build made from a checkout.
Upgrading
CHANGELOG.md ships inside the wheel and the sdist. It is migration-guide
style: every breaking change is written as what it was, what it is now, and the
exact edit with before/after code. Read it before a version bump.
from importlib.resources import files
print(files("warmhub").joinpath("CHANGELOG.md").read_text(encoding="utf-8"))
Release notes are also published at https://docs.warmhub.ai/releases/overview/.
Quickstart
from warmhub import WarmHubClient
with WarmHubClient.from_env() as client: # reads WH_TOKEN
repo = client.repository("acme/sensors")
page = repo.things.head(shape="Reading", limit=5)
for item in page.items:
print(item.wref, item.version)
WarmHubClient(access_token=...) is the explicit form. The default constructor
reads no environment variable — only from_env() does, and it says so in the
name.
Writing
batch = repo.batch(message="seed readings")
batch.add(name="Reading/probe-1", data={"temp_celsius": 21.4})
result = batch.commit() # the only line that issues a request
Submission is explicit. Nothing here performs network I/O on scope exit.
Preview the same operation array with the real server commit evaluator before submitting it:
preview = repo.validate(
[
{
"operation": "add",
"kind": "thing",
"name": "Reading/probe-2",
"data": {"temp_celsius": 19.8},
}
],
message="seed probe-2",
include_would_be_body=True,
)
if not preview.can_commit:
for operation in preview.operations:
if operation.status == "error":
print(operation.errors)
repo.validate(...) is the repo-bound spelling of
client.commit.validate(org_name, repo_name, operations, ...); the async client
has the same method and awaits it. Validation sends one bounded request (at most
10,000 operations and 4 MiB encoded), creates no durable repository state or
receipt, and returns ordered would_apply, noop, or error results. It is a
snapshot rather than a reservation: concurrent writes, write-time admission, and
asynchronous actions are not projected.
A revise takes expected_version to make the write conditional on the version
you read. The commit fails rather than clobbering a concurrent write:
repo.batch(message="correct probe-1").revise(
name="Reading/probe-1",
data={"temp_celsius": 21.7},
expected_version=3,
).commit()
Assertions
An assertion is a thing that makes a shape-validated claim about another thing. It is the write that makes a repository a knowledge graph rather than a table, and it is queryable like any other thing:
repo.batch(message="flag the outlier").add(
name="Suspect/probe-1-spike",
kind="assertion",
about="Reading/probe-1",
data={"confidence": 0.8, "reason": "exceeds calibrated range"},
).commit()
about accepts any wref — a specific thing (Reading/probe-1) or a whole
shape (Reading).
Ask the other direction with thing.about, called on the subject's
repository. The assertions may live somewhere else entirely, and nothing in the
call names where:
filed = client.thing.about("acme", "sensors", "Reading/probe-1", limit=25)
if filed.target is not None: # the subject itself, optional
print(filed.target.wref, filed.target.version)
for claim in filed.assertions:
print(claim.shape_name, claim.wref, "->", claim.about_wref)
One request returns both halves, so "fetch it, then fetch what people said
about it" is not two round trips. Anyone can assert about your records without
your repository knowing they exist; this is how you find out. .next_cursor
pages exactly like head does.
Querying
head reads current state, filtered. where builds predicates through
operator overloading, and a dotted path reaches nested fields:
from warmhub import where
page = repo.things.head(
shape="Reading",
where=[where("temp_celsius") > 30, where("sensor.county") == "Marin"],
limit=100,
)
Results are paginated. head_iter walks the pages for you, and head_all
collects them under an explicit ceiling:
for item in repo.things.head_iter(shape="Reading"):
print(item.wref, item.version)
everything = repo.things.head_all(shape="Reading", max_items=10_000)
Typed reads
data is your shape's payload, so the SDK types it JsonValue — it genuinely
does not know your shape. When you do, say so with decode_as and get a page
of frozen dataclasses, statically as well as at runtime:
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Reading:
temp_celsius: float
probeId: str
page = repo.things.head(shape="Reading", decode_as=Reading)
page.items[0].data.temp_celsius # a Reading, not a dict
No key is transformed, in either direction. Field names are looked up verbatim, so a camelCase wire field needs a camelCase attribute. That reads oddly in Python and it is the right trade: a client that guessed at case conversion would be a client that can silently corrupt a repository. Declaring a subset is fine — extra keys are ignored, and only the fields you declare must be present.
Async
Every surface has an async twin with an identical signature:
from warmhub import AsyncWarmHubClient
async with AsyncWarmHubClient.from_env() as client:
repo = client.repository("acme/sensors")
page = await repo.things.head(shape="Reading", limit=5)
The sync and async clients emit byte-identical HTTP requests for the same call, and a test asserts it.
Two rules worth knowing up front
Everything returned is a frozen object with snake_case attributes. Unknown
fields a newer backend adds land in .extra rather than being dropped. .data
is a plain mapping, because those keys are your own shape fields and the client
never transforms them.
Omitted is not null. Optional arguments default to UNSET, not None.
Passing None means "set this to null" on the wire. bool(UNSET) raises, so
if limit: cannot silently conflate "not provided" with 0.
Documentation
Release files for warmhub 0.14.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| warmhub-0.14.1.tar.gz | 329.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| warmhub-0.14.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 727.6 kB
Release files / warmhub-0.14.1.tar.gz
| Download URL | warmhub-0.14.1.tar.gz |
|---|---|
| Size | 329.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7806bf01e52658b9369031b3b71c6a40f52292c0c6ed4797d17d274cdf4b6225
|
|
BLAKE2b-256 checksum How to use checksums |
e656cbc6309f8f71d012c275a68df960d49fbf7da566d07b08d7056283bb3429
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / warmhub-0.14.1-py3-none-any.whl
| Download URL | warmhub-0.14.1-py3-none-any.whl |
|---|---|
| Size | 398.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
12e9e54cadee16829a1e7693ad67c18154a5b682dbc167f403638e91e21e2e63
|
|
BLAKE2b-256 checksum How to use checksums |
e7ffcd239d4ea5689b781f5882a3cd4a81f4bd7da63d0c25a474a37a3fc1954a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log