Skip to main content

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.

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.10.5

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

Source distribution (sdist)

Source distribution for warmhub 0.10.5
File Size Uploaded
warmhub-0.10.5.tar.gz 303.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for warmhub 0.10.5
File Interpreter ABI Platform
warmhub-0.10.5-py3-none-any.whl Python 3 none any Details

Total release size: 671.9 kB

Release files / warmhub-0.10.5.tar.gz

Download URL warmhub-0.10.5.tar.gz
Size 303.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2bb1b7b800899ac2e72b9282f672788f5a320a96db4cad1000f8ef155728b3d4
BLAKE2b-256 checksum
How to use checksums
2a04bddd187789032ef6c895a768d1fc374648660f2cb30085a42ff8a8df8169
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 Aug 31, 2026.

Transparency log

Release files / warmhub-0.10.5-py3-none-any.whl

Download URL warmhub-0.10.5-py3-none-any.whl
Size 368.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f2694146deb7bb04e98e6c4f14d71d3e55f495837bc0c39bc9db0772cfee08df
BLAKE2b-256 checksum
How to use checksums
93c2c7b5a6ad1008d85cd2bd277d65afbac7d9f9a475d61c03e5be19d2225716
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 Aug 31, 2026.

Transparency log

Release history Release notifications | RSS feed

0.17.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.1

2 release files

0.15.0

2 release files

0.14.4

2 release files

0.14.3

2 release files

0.14.2

2 release files

0.14.1

2 release files

0.14.0

2 release files

This release

0.10.5 This release

2 release files

0.10.4

2 release files

0.10.3

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.0.1

2 release files

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