Released docs. You are viewing the documentation published with v0.25.0. Development docs are available at Latest.
Query Registry Relay with Python
For the consumer or verifier
The Registry Relay Python client gives a consumer or verifier one synchronous method for each fixed Relay V2 operation. In this tutorial you will install a released wheel, read one synthetic business record, advance discovery only when Relay returns a continuation, revalidate OpenAPI with a strong entity tag, and inspect a governed refusal.
Before you start
Section titled “Before you start”Complete Publish a governed SQLite registry through
the Serve it step. Leave Relay running and open a second shell. The resulting deployment is
anonymous, contains synthetic records only, and listens at http://127.0.0.1:8080.
That second shell also needs the relay binary on its path. The install below reads the exact
release version from it rather than asking you to type one.
Prebuilt Relay client packages start with Registry Stack v0.20.0. The running Relay must therefore be v0.20.0 or later. Install the client from that exact Relay release, using the wheel that matches this machine.
Install the client and read a record
Section titled “Install the client and read a record”Work in a directory of its own, so the wheel and the virtual environment stay out of the registry project you built in the prerequisite:
mkdir relay-python-querycd relay-python-querypython3 -m venv .venv. .venv/bin/activateNow read the release version from the running Relay, select the wheel for this machine, and install it. The release workflow attaches Python wheels to the matching GitHub Release, so the download comes from that release rather than from PyPI:
VERSION="$(relay --version | awk '{print $2}' | sed 's/^v//')"case "$VERSION" in *-dev*) echo "This relay was built from source ($VERSION), not installed from a release" >&2; exit 1 ;;esaccase "$(uname -s)-$(uname -m)" in Linux-x86_64) WHEEL_PLATFORM="linux_x86_64" ;; Linux-aarch64|Linux-arm64) WHEEL_PLATFORM="linux_aarch64" ;; Darwin-arm64) WHEEL_PLATFORM="macosx_11_0_arm64" ;; *) echo "No prebuilt Relay client wheel for this platform" >&2; exit 1 ;;esacWHEEL="registry_relay_client-${VERSION}-cp310-abi3-${WHEEL_PLATFORM}.whl"curl -fLO "https://github.com/registrystack/registry-stack/releases/download/v${VERSION}/${WHEEL}"python -m pip install "./${WHEEL}"The wheel uses the Python 3.10 stable ABI, so supported newer Python versions import the same
file. The three platform names in that case are the whole set: the release provides no Windows
or Intel macOS wheel.
The first case stops a build that came from source rather than from a release. Such a build
reports a -dev version, which names no published release and so has no wheel to download.
Create query_relay.py. It constructs a client against the running deployment and reads one
record:
import json
from registry_relay_client import RelayClient
client = RelayClient(base_url="http://127.0.0.1:8080")result = client.read_record("registered-business", "BIZ-0001")
print(result["kind"])print(result["value"]["data"]["recordIdentifier"])print(json.dumps(result["value"]["data"]["domainData"], sort_keys=True))Run it:
python query_relay.pycompleteBIZ-0001{"legalForm": "COOPERATIVE", "legalName": "Aurora Freight Cooperative"}complete distinguishes a returned representation from a cache revalidation response. The record
envelope stays a plain Python mapping, so reading it is ordinary dictionary access and the client
carries no schema for your records. domainData holds only the two fields the synthetic Relay
contract discloses, not the registered address and registrar note stored beside them in the same
row.
Pass fields=["legalName"] to read_record to narrow that response further. Narrowing is the only
direction available: a caller cannot use fields to widen the contract’s disclosure profile. Read
the Relay client API reference before adding authentication or
private certificate roots.
Handle an optional continuation exactly
Section titled “Handle an optional continuation exactly”Relay returns a continuation only when a further page exists, so a caller has to branch on its
presence instead of assuming it. Append this discovery loop to query_relay.py, then run the file
again:
page = client.resources(page_size=1)
while True: for resource in page["value"]["items"]: print(resource["resourceIdentifier"])
continuation = page["continuation"] if continuation is None: break
page = client.continue_resources(continuation)The run ends with one new line:
registered-businessThis deployment has one resource, so its first page has no continuation and the loop breaks on the
first pass. On a deployment with more resources, continuation is exactly
{"cursor": "<opaque-cursor>"}. Pass that returned mapping unchanged to continue_resources. Do
not extract its cursor, rebuild the mapping, or add first-page options.
Record-list and search pages use route-bound continuation mappings. Hand those unchanged to
continue_list_records or continue_search, respectively. The client never advances a page on
its own.
If you persist a continuation between runs, persist the complete returned mapping. The matching continuation method validates it again, so a resource, record-list, or search continuation cannot be substituted for another route.
Revalidate OpenAPI with a strong entity tag
Section titled “Revalidate OpenAPI with a strong entity tag”The OpenAPI document is cacheable, which lets a second request ask Relay whether the copy you already hold is still current. Append this conditional request and run the file:
first = client.openapi()print(first["kind"])print(first["etag"] is not None)
second = client.openapi(etag=first["etag"])print(second["kind"])print(second["etag"] == first["etag"])The run ends with four new lines:
completeTruenot_modifiedTrueThe first response carries raw OpenAPI bytes and a validated strong ETag. The second call sends
that tag as If-None-Match. Relay answers 304 Not Modified, and the binding returns a
not_modified outcome with the echoed tag and trace identifier instead of a body. Nothing in that
second outcome carries the document, so a caller that discarded the first body has nothing left to
serve.
Store the complete response body and ETag together, then reuse the stored body only when the next
outcome is not_modified and its ETag matches. Revalidation stays explicit for every cacheable
method: the client sends If-None-Match only on a call where you passed etag.
Handle a Relay refusal
Section titled “Handle a Relay refusal”A record identifier the register does not hold is a refusal, not an empty result. Append this request for a missing synthetic record and run the file:
from registry_relay_client import RelayClientError
try: client.read_record("registered-business", "BIZ-9999")except RelayClientError as error: print(error.kind) print(error.status) print(error.code)The run ends with three new lines:
problem404consultation.unresolvedA valid Relay Problem becomes RelayClientError with stable, value-free attributes. The client
does not include response bodies, request selectors, credentials, URLs, or header values in the
error. Only a registered 429 Problem can carry retry_after_seconds.
Branch on kind first, then use status and code when they are present. Treat trace_id as the
correlation value to give an operator. Decide at your application boundary whether a request is
safe to repeat, because the client performs no automatic retries.
Clean up
Section titled “Clean up”Leave the virtual environment and remove the tutorial directory:
deactivatecd ..rm -rf relay-python-queryStop Relay with Ctrl+C in its shell if it was running only for this tutorial. The synthetic
registry project remains available for later Relay exercises.
What you built
Section titled “What you built”- A synchronous Python consumer that delegates Relay routing, response validation, and bounded body handling to the canonical Rust client.
- An explicit branch that passes an optional continuation unchanged when Relay returns one, with no implicit pagination.
- A strong-ETag revalidation path that distinguishes complete and
304outcomes. - A refusal path that uses stable error facts without exposing request or credential values.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
GitHub returns 404 for the wheel | The running Relay is older than v0.20.0, its exact release is not published, or the platform name is wrong | Use a published v0.20.0 or later Relay, then download the wheel from that exact release for one of the three platforms in this tutorial. |
built from source, not installed from a release | relay --version reports a -dev version, which every build outside the release workflow does | Install relay with the installer in the prerequisite, or download a client wheel from whichever release you want a client for. |
No matching distribution found | The wheel filename or local path does not match the downloaded asset | Keep the original release filename and install that exact local file. |
Connection refused | The synthetic Relay is not listening on port 8080 | Return to the publishing tutorial, start Relay, and leave it running in its shell. |
configuration at construction | The base URL is not HTTPS or loopback HTTP, or it contains credentials, a query, or a fragment | Use http://127.0.0.1:8080 for this local deployment. |
protocol during a response | Relay returned a response outside the fixed media type, trace, ETag, Problem, or body contract | Record trace_id when present and inspect the Relay operator logs. Do not parse the rejected body in application code. |
A repeated call returns not_modified | The supplied strong ETag still identifies the current representation | Reuse the body stored with that same ETag. |
- Read the Relay client API reference for every Rust, Python, and Node operation and outcome shape.
- Author a Registry Relay project to add list, lookup, search, artifact, and SDMX operations to a deployment.
- Review errors and status codes before mapping failures into an application-facing API.