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.

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/sdk/python/changelog/.

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("probe-1", shapes=["Reading"], 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.

A write names a Thing by its full name (taken literally, / included) and states its Shapes in exactly one way, or it is refused with a WriteOperationError (VALIDATION_ERROR, with operation_index) before any request:

from warmhub import Add, Rename, Retract, Revise, shape_name

repo.apply(
    "seed",
    [
        Add(name="acme", shapes=["Deal"], data={"company": "Acme"}),
        Add(name="scratch", shapeless=True, data={"note": "free"}),
        Revise(name="acme", keep_shapes=True, data={"company": "Acme 2"}),
        Add(name="r1", shapes=["Rating"], data={"about": "acme", "score": 5}),
        Add(name="crew", shapes=["Set"], data={"members": ["acme"]}),
        Rename(name="scratch", new_name="scratch-2"),
        Retract(name="crew", reason="disbanded"),
        # A Shape is the Thing hub/shapes/X, written with shapes=[].
        Add(name=shape_name("Deal"), shapes=[], data={"fields": {"company": "string"}}),
    ],
)
  • shapes=["Deal", "Contactable"]: bare Shape names. A pinned Deal@v2 is refused: a write always certifies under each Shape's head version.
  • shapeless=True: no Shape; the data is stored but not validated, and the operation result carries warnings.shapeless.
  • keep_shapes=True (Revise only): the SDK reads the Thing's current Shapes and version and sends both, the version as expectedVersion (a supplied expected_version wins). The Shapes are sent bare, so the revise re-certifies under each Shape's head version. A Thing revised earlier in the same submission carries that revise's Shapes and no version; one added, or renamed to or from, earlier in the same submission is refused before anything is sent (submit it in a later request, or pass shapes). keep_shapes pins the version it read; a cross-process resubmit of the same submission_id can conflict; explicit shapes is the retry-safe form.

The v1 keyword arguments (kind, about, type, members, wref) are gone; passing one raises a TypeError naming the v2 spelling, and a v1-shaped mapping is refused with its v2 form in the message.

Preview the same operation array with the real server commit evaluator before submitting it:

preview = repo.validate(
    [
        {
            "operation": "add",
            "name": "probe-2",
            "shapes": ["Reading"],
            "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(
    "probe-1",
    shapes=["Reading"],
    data={"temp_celsius": 21.7},
    expected_version=3,
).commit()

Assertions

An assertion is a thing that makes a shape-validated claim about another thing: a Thing one of whose declared Shapes composes Assertion, with its target in data["about"]. Writing one requires the server's composite-Shapes write setting to be on. 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(
    "probe-1-spike",
    shapes=["Suspect"],
    data={
        "about": "probe-1",
        "confidence": 0.8,
        "reason": "exceeds calibrated range",
    },
).commit()

data["about"] accepts any wref — a specific thing (probe-1) or a whole shape (hub/shapes/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", "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.declared_shapes, 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

Metadata

Release files for warmhub 0.27.2

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.27.2
File Size Uploaded Provenance Actions
warmhub-0.27.2.tar.gz 384.0 kB With provenance Details for warmhub-0.27.2.tar.gz

Built distribution (wheel)

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

Total release size: 849.3 kB

Release files / warmhub-0.27.2.tar.gz

Download URL warmhub-0.27.2.tar.gz
Size 384.0 kB
Tags Source
SHA-256 checksum
How to use checksums
1193ed29e38e31eac43bfad0595951f5d0bc6ec73b93f8c82e068af2ba53bcf6
BLAKE2b-256 checksum
How to use checksums
bcc9313898ff23a4f3e1b1371c8857c22e492cad3e99c1a8eecdfe14b0c940d9
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 Oct 21, 2026.

Transparency log

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

Download URL warmhub-0.27.2-py3-none-any.whl
Size 465.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
98c4846a8648bc5f5837b6e763a5b3e0c3fb0eb221a959c2c9d033eedf7c838e
BLAKE2b-256 checksum
How to use checksums
f8158c1a5255e56b91a2638ad62f3140d14fe851a4b1c1fcea603b243ed6c9b5
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 Oct 21, 2026.

Transparency log

Security for warmhub 0.27.2

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for warmhub-0.27.2.tar.gz

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 Oct 21, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.27.2 This release

2 release files Full 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