Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python Client

quipu-client is a thin, typed Python client for the REST API, living in python/ in the main repository. Standard library only — urllib all the way down, zero runtime dependencies, Python >= 3.11. It is a wrapper, not a re-implementation: request shapes, auth, and error surfaces are kept honest against the REST reference, and anything not listed here is a call away with curl.

Installation

# from a quipu checkout
pip install ./python

Quick start

from quipu_client import QuipuClient, QuipuError

q = QuipuClient("http://localhost:3030", token="secret-for-writes")

q.health()      # {"status": "ok"}
q.stats()       # {"facts": ..., "entities": ..., "predicates": ...}
q.version()     # {"version", "git_sha", "git_dirty", "features"}

Reads are open; writes need a bearer token — the client mirrors that contract exactly. token is attached as Authorization: Bearer <token> on write calls only, and a client constructed without a token sends no Authorization header at all. The optional client_id / task_id constructor arguments become the X-Quipu-Client / X-Quipu-Task attribution headers.

Reads

MethodEndpointReturns
health()GET /healthdict
stats()GET /statsdict
version()GET /versiondict
query(sparql, graph=, valid_at=, tx=, fork=, include_kinds=, federated=)POST /querydict
validate(shapes, data)POST /validatedict
search(embedding=, query=, limit=, valid_at=, group_ids=, entity_type=)POST /searchdict
hybrid_search(sparql, embedding, limit=)POST /hybrid_searchdict
context(query, max_entities=)POST /contextdict
ask(name=, params=)POST /askAskResult (dict when listing the catalog)
export(graph=, group_id=, construct=, format=)POST /exportstr — the RDF document itself, not JSON

Unset optionals are omitted from the request body entirely, never sent as null — to /query, absent and null are different things (silence never widens scope).

Writes

MethodEndpointReturns
knot(turtle, shapes=, timestamp=, valid_from=, actor=, source=, graph=, ...)POST /knotKnotResult(tx_id, count, conforms, valid_from)
episode(name, nodes=, edges=, replace_snapshot=, source=, timestamp=)POST /episodeEpisodeResult(outcome, count, tx_id)
set(entity, predicate, value, timestamp=, actor=)POST /setSetResult(tx_id, retracted, asserted, ...)
retract(entity, predicate=, value=, timestamp=, actor=)POST /retractRetractResult(retracted)

Every result dataclass keeps the full decoded body in .raw.

Two REST-doc contracts worth restating because the types encode them:

  • Branch on EpisodeResult.outcome, never on count. unchanged is a successful idempotent retry — re-posting under a new name because count == 0 looked like failure is how duplicate entities get minted.
  • An edge value is {"iri": ...}, a bare string is a literal. set and retract pass value through untranslated, so the server’s loud 400 for a bare IRI-shaped string reaches you as a QuipuError with the guidance attached.

Errors: QuipuError

Every non-2xx answer raises QuipuError — a refusal is never silent, and never swallowed:

try:
    q.knot(turtle)
except QuipuError as e:
    e.status    # HTTP status code
    e.body      # decoded JSON body (or raw text when not JSON)
    e.reason    # the stable machine-readable field, e.g.
                # "missing_or_invalid_bearer_token" — None if absent

A SHACL refusal’s feedback payload — which shape, which constraint, which focus node — arrives intact in e.body and in str(e), because that feedback is the entire point of the refusal.

Tests

The suite runs against a stdlib http.server stub asserting method, path, headers, and body per call — no network, no live Quipu:

python3 -m pytest python/tests -q