Skip to main content

munarium-client (Python)

Client 1.3.0 targets Munarium Server 1.3.0 with declared support for Server minors 1.3 and 1.2; see the compatibility record.

Official Python client for munarium-server: the full ten-plane surface (commands, query, ingest, retrieval, runbooks, providers, sessions, tokens, reports, authoring), sync and async, both transports, typed exceptions, the head-conflict write loop built in. See the clients front door for the invariants, the transport-gap ledger, and guides.

Python ≥ 3.11 · fully typed (py.typed, mypy --strict clean).

Install

The registry examples below pin the recorded published release. Client 1.3.0 is unreleased; use the source installation instructions for its Server 1.3 APIs.

Install munarium-client from PyPI with Python 3.11+:

python -m pip install munarium-client==1.1.1

Or install from the repository root of a complete checkout:

python -m pip install ./clients/python
# For client development:
python -m pip install -e "./clients/python[dev]"

Published versions are recorded in the clients front door.

Use

from munarium_client import (
    AsyncMunariumClient, ClientOptions, HeadConflictError, MunariumClient,
)

# sync + REST
client = MunariumClient.rest(
    ClientOptions("http://127.0.0.1:8080", token="devtoken", uid="user-1"))
# …or sync + gRPC / async + REST / async + gRPC
client = MunariumClient.grpc(ClientOptions("127.0.0.1:50051", token="devtoken", uid="user-1"))
aclient = AsyncMunariumClient.rest(
    ClientOptions("http://127.0.0.1:8080", token="devtoken", uid="user-1"))

v = client.commands.create_version()

# Disputed is SUCCESS — the governance record, not an error.
outcome = client.commands.propose_claim(v, subject="hero", key="eyes", value="blue")
if outcome.is_disputed:
    for f in outcome.findings:
        print(f.rule_id, f.message)

# The write loop: expected_head + fresh idempotency key per attempt.
outcome = client.propose_claim_with_retry(
    v, lambda head: {"subject": "hero", "key": "home", "value": "harbor"})

# One pin bounds all stores.
page = client.query.facts(v, as_of_seq=1)

uid is the acting end-user id (audit attribution), required by the server's default posture (MUNARIUM_REQUIRE_UID=true) — omit it and every call draws the typed uid-required error.

The platform surface stays Pythonic: sessions.turn_stream(...) is a plain Iterator (async: an async generator) of TurnProgress events whose last item is the TurnResult — typed errors raise during iteration, and a stream that ends without a terminal event raises TransportError. If you may leave the ASYNC stream early, wrap it in contextlib.aclosing(...) so the pooled connection is released deterministically instead of whenever the garbage collector finalizes the abandoned generator:

from contextlib import aclosing
from munarium_client.models import TurnProgress

async with aclosing(aclient.sessions.turn_stream(sid, query="vacation policy")) as events:
    async for event in events:
        if isinstance(event, TurnProgress) and event.stage == "model":
            break

On gRPC turn_stream is REST-only: the sync plane raises UnsupportedError when called, and AsyncMunariumClient.grpc(...).sessions.turn_stream is a real async generator that raises it on the first iteration. When the 60 s SSE idle watchdog fires, the TransportError says the turn may still be executing server-side (the completion was paid) — read the transcript with sessions.get before re-sending. Unary turns are deadline-exempt and never auto-retried (they spend provider tokens a client abort cannot stop); bulk upload sessions ride ingest.bulk_open/bulk_chunk/bulk_complete; tokens/reports/authoring cover the management plane (mint with a mgmt-role bearer).

providers.max_tokens() / providers.replace_max_tokens(budgets) read and replace the tenant's per-call output-token budgets (GET/POST /v1/max-tokens). MaxTokensResponse is the eight MaxTokensBudgets fields flattened beside source (tenant | environment) and updated_at, and it subclasses the budgets model, so a read result edited in place round-trips into the replacement — only the eight budget fields go on the wire. There is no partial update: a dict missing a field is refused before it is sent, an out-of-range value is the server's InvalidInputError, the replace needs the static rw role (ForbiddenError otherwise), and both are REST-only (UnsupportedError on gRPC).

sessions.turn/turn_stream take an optional research_profile=: the turn runs through a named evidence hierarchy and TurnResult.hierarchy carries the decision — which layers ran, which refused, whether a completeness claim was permissible at all. Omit it and nothing changes: the request body grows no key and the response carries no hierarchy. A streamed turn under a profile adds the profile / layer_start / layer_source / layer_complete / coverage / compose stages after the existing ones (TurnProgress declares only stage, so per-stage fields ride as extras). On the operator side reports.evidence(window=) shows which layer is quietly refusing — those turns still return 200, so no error rate reveals them — and reports.matrix() reports Matrix's reachability, keeping configured=False (never wired) distinct from circuit_open=True (tripped).

Exceptions mirror the problem-slug registry (HeadConflictError, PolicyRejectionError with findings + truncation markers, RunLockedError — typed but NOT transient, pace it like a rate limit — RateLimitedError.retry_after — populated only if the server sends a Retry-After header, which it does not today). UnsupportedError marks the documented gRPC gaps.

Command retry is deliberately narrow: on REST a command re-sends its SAME idempotency key only after a connect-phase failure (httpx.ConnectError / ConnectTimeout / ProxyError — the request never left) or the typed OverloadedError (shed before executing); a gateway 502/504 is transient for reads but never re-sent as a command, because it may still be executing upstream. On gRPC commands re-send only on OverloadedError — never on a transport failure. Two gRPC input rules beyond the proto3 zero sentinels: ingest() mirrors REST POST /v1/ingest (a locally undecodable content_base64 raises InvalidInputError; a server-side per-item error on that one file raises UnexpectedError with the text — the wire has no slug; ingest_batch keeps per-item results), and an explicit collections=[] on an ingest file or runbook_refs=[] on tokens.mint raises InvalidInputError (proto3 cannot carry "explicitly empty"; pass None or use REST). Both transports accept base64 with surrounding whitespace.

The async gRPC variant drives the thread-safe sync stubs via asyncio.to_thread (documented implementation choice); async REST is native httpx.

Regenerating the gRPC stubs

The generated stubs under src/munarium_client/_proto/ are committed and CI-drift-checked. After a proto change:

python scripts/gen_protos.py

Tests

pytest tests                                # offline unit tests
MUNARIUM_REST_URL=http://127.0.0.1:18080 MUNARIUM_GRPC_URL=127.0.0.1:15051 \
MUNARIUM_TOKEN=devtoken MUNARIUM_MGMT_TOKEN=devmgmt \
pytest conformance     # scenarios x {rest,grpc} x {sync,async} + smokes
                       # + the platform tests (skipped without the mgmt token)

Metadata

Release files for munarium-client 1.3.0

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

Source distribution (sdist)

Source distribution for munarium-client 1.3.0
File Size Uploaded
munarium_client-1.3.0.tar.gz 155.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for munarium-client 1.3.0
File Interpreter ABI Platform
munarium_client-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 302.5 kB

Release files / munarium_client-1.3.0.tar.gz

Download URL munarium_client-1.3.0.tar.gz
Size 155.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2139e42a2f389568718b0bb8fbb7d2d07833ebc1a2186716f18302de4834a611
BLAKE2b-256 checksum
How to use checksums
7aeb5f178d2a0384dea7a598d3c60cd3e3983b7eab5ad2d1452eef7eda908446
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 27, 2026.

Transparency log

Release files / munarium_client-1.3.0-py3-none-any.whl

Download URL munarium_client-1.3.0-py3-none-any.whl
Size 146.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b33e19df5b096847cb28fdd47378f37cde13e75d0a0ddaf414812a59e59f5004
BLAKE2b-256 checksum
How to use checksums
61d5e21bc00106d394ac39fd39d31f649d30743c61c0b906d1733299b6b188de
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 27, 2026.

Transparency log

Release history Release notifications | RSS feed

1.4.0

2 release files

This release

1.3.0 This release

2 release files

1.1.1

2 release files

1.1.0

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