Zanii Mandate Python SDK
Python 3.12+; standard-library runtime only.
python -m pip install ./packages/sdk-python
import os
from zanii_mandate import MandateClient
client = MandateClient(
verify_base_url=os.environ["ZANII_VERIFY_URL"],
api_key=os.environ["ZANII_API_KEY"],
timeout=5.0,
cache_capacity=128,
cache_ttl=5.0,
)
decision = client.verify({
"mandateId": os.environ["ZANII_MANDATE_ID"],
"request": {
"agentDid": os.environ["ZANII_AGENT_DID"],
"action": "supplier.read",
"resource": "supplier:acme",
},
}, operation_id="checkout-attempt-2026-001")
assert decision["verdict"] == "allow"
Use verify_batch(items, operation_id=...) for 1–100 items. Reuse the same caller-owned operation ID only for an identical retry. It maps to Idempotency-Key; replay does not consume quota twice.
Verdicts are returned values. MandateApiError, MandateTransportError, MandateTimeoutError and MandateResponseError distinguish HTTP, network, deadline and malformed/oversized-response failures. Public keys() and status() metadata use independent bounded single-flight TTL/LRU caches; verdicts are never cached. An optional tracer exposes the standard start_as_current_span(name, attributes=...) OpenTelemetry shape.
Typed verdicts and reason codes (0.2.0)
Verdicts stay plain dicts, so decision["verdict"], decision["checks"] and fields a newer server adds keep working. zanii_mandate.types adds TypedDict hints for type checkers: Verdict (with conditions: list[ConditionResult], each index, type, ok, code, reason, evidence), Reason, VerifyInput, VerifyRequest (agentDid, action, resource, amount: {"value", "currency"}, dataClass, jurisdiction, approvalId(s), dataSubject, counterpartyDid, dataRegion, destination, transferSafeguard) and Amount. verify accepts a VerifyInput or any dict as before. MandateApiError carries status, code, detail and the server details.
zanii_mandate.codes defines one constant per verdict reason and API error code (codes.ACTION_DENIED == "ACTION_DENIED"), plus the REASON_CODES set, equal to the shared list tests/fixtures/reason-codes.json that the Go, Java and Node.js SDKs also follow. Codes are open strings: never treat an unlisted code as success.
Unknown outcomes
is_unknown_outcome(error) is true when the server answered DATABASE_COMMIT_UNKNOWN (HTTP 503: the connection was lost during COMMIT, so the write may or may not have landed) or when a timeout or transport failure happened after the request may have reached the server. A refused connection or DNS failure is false. Every MandateTimeoutError counts as unknown, because urllib cannot tell a connect timeout from a slow answer. When it is true, mark the operation unknown (mark_operation_unknown) or read the state back with the same operation ID, and never redo the business action. Re-sending the same verify with the same operation ID only replays the stored result.
import json
from zanii_mandate import MandateError, is_unknown_outcome
try:
client.commit_operation(operation_id, outcome)
except MandateError as error:
if is_unknown_outcome(error):
client.mark_operation_unknown(operation_id, {"payload": json.dumps({"reason": "commit outcome unknown"})}) # text, not a dict
raise # reconcile later; do not repeat the action
raise
Example:
PYTHONPATH=packages/sdk-python/src python packages/sdk-python/examples/verify.py checkout-attempt-2026-001
Conformance and package commands:
python -m unittest discover -s packages/sdk-python/tests -v
python -m pip wheel --no-deps --no-build-isolation packages/sdk-python
Operation-bound FastAPI and Django middleware
Install the framework extra (zanii-mandate-sdk[fastapi] or zanii-mandate-sdk[django]). Import mandate_operation from zanii_mandate.fastapi_middleware or zanii_mandate.django_middleware and decorate a route/view with client and a server-owned operation mapping. The asynchronous FastAPI decorator runs the synchronous SDK transport in worker threads; the Django decorator supports synchronous views.
The mapping uses the operation API field names: mandateId, agentDid, action, audience, enforcementPoint, policyRevision, authorityEpoch, argumentSchema, and either static resource or resources plus resourceArgument. Optional fields are mandateVersionId, amount/amountArgument + currency, and count. String, decimal-string, boolean, and non-negative safe-integer arguments are allowlisted. Python decimal strings are required instead of non-integer JSON floats so canonical argument bytes remain interoperable with the Node JCS profile.
Request JSON may contain only args, stable expiresAt, approvalId, and approvalIds; caller claims for action/resource/identity/policy are rejected. Supply the stable operation ID in Idempotency-Key or X-Zanii-Operation-Id. Both adapters reserve and dispatch through MandateClient before calling the endpoint/view. Only fresh allow + dispatched executes. Deny/review return reason-bearing 403 responses; OPERATION_MISMATCH stays 409; timeouts return 504; transport/verification errors never execute.
A 2xx result is committed with the reserved amount/count and an SDK-observed outcome payload. Non-2xx results and endpoint/view exceptions are marked unknown. This is usage accounting, not a signed external execution receipt. An operation replay returns the stored operation result with Idempotency-Replayed: true, without executing again or consuming usage; it does not reconstruct the arbitrary original endpoint/view response. Commit failure cannot undo an external effect.
Executed locally: 17/17 Python tests passed at T7 acceptance (27 October 2026): the original 7 SDK tests over all 12 shared real-PostgreSQL/HTTP vectors plus 10 real FastAPI/Django middleware tests. Both frameworks ran actual loopback HTTP servers against the real verify-api and isolated PostgreSQL 17.11. Tests cover allow/exact accounting, replay without re-execution, argument tampering, deny/review reason propagation, verification error, timeout, and trusted mapping. Django 5.2.8 was installed into the ignored .local/t7-python-deps test directory; no global package was installed. A wheel built successfully. The T5-3 offline evaluator is provided by the Node package; no Python offline cryptographic verifier, package-index publication, production deployment, or exactly-once remote-effect guarantee is claimed.
Changelog
0.2.1 (30 October 2026)
- Documentation fix only, no code change: the unknown-outcome example now sends the operation payload as
text (
json.dumps({"reason": ...})). The server refuses an object payload withINVALID_OUTCOME, so the 0.2.0 example would have failed when used.
0.2.0 (30 October 2026)
zanii_mandate.codesconstants,REASON_CODESandUNKNOWN_OUTCOME_CODES, equal to the shared listtests/fixtures/reason-codes.json(130 codes) used by all four SDKs.is_unknown_outcome(error), with the Go and Java semantics.TypedDicthints (Verdict,ConditionResult,Reason,VerifyInput,VerifyRequest,Amount); verdicts remain plain dicts. No change for 0.1.0 callers.
0.1.0
- First release: verify and batch, operation lifecycle, key and status caches, telemetry, FastAPI and Django middleware.
Metadata
Release files for zanii-mandate-sdk 0.2.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 | Provenance | Actions |
|---|---|---|---|---|
| zanii_mandate_sdk-0.2.1.tar.gz | 27.2 kB | Without provenance | Details for zanii_mandate_sdk-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| zanii_mandate_sdk-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for zanii_mandate_sdk-0.2.1-py3-none-any.whl |
Total release size: 48.9 kB
Release files / zanii_mandate_sdk-0.2.1.tar.gz
| Download URL | zanii_mandate_sdk-0.2.1.tar.gz |
|---|---|
| Size | 27.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
63f290f576dee0cfc8d1125b930542217614bec6d959de478ab66573defcd6bd
|
|
BLAKE2b-256 checksum How to use checksums |
7df59b86123f8813b1c174173432b0c48d6ee04c13de69f684f8d20cd032f534
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / zanii_mandate_sdk-0.2.1-py3-none-any.whl
| Download URL | zanii_mandate_sdk-0.2.1-py3-none-any.whl |
|---|---|
| Size | 21.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4242b9f05a814b93dfb1593ff82afb8e4581d96b4f187dc3e19cbe382db17347
|
|
BLAKE2b-256 checksum How to use checksums |
3e6c3a5e58eafd2014a368564b2b67592bdf3c2e6c27ccef109cb0b7c40dafbd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Security for zanii-mandate-sdk 0.2.1
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.