registry-discovery-client-py
Synchronous PyO3 binding for the bounded Rust registry-discovery-client SDK.
It performs exact service search, Evidence Type resolution, and ambiguity-safe
selection. A returned selection is inert public metadata. Apply local trust
policy before calling its Evidence or Relay endpoint.
Starting with Registry Stack v0.23.0, install the exact client version that matches the Discovery deployment:
python -m pip install "registry-discovery-client==<version>"
Published v0.23.0 and later distributions carry manylinux wheels requiring glibc 2.17 or newer for Linux amd64 and Linux arm64, plus a macOS arm64 wheel.
The public methods accept only ordinary built-in JSON values: None, bool,
signed 64-bit int, finite float, str, list, and dict with string keys.
They reject custom objects, subclasses, tuples, cycles, and oversized values
before sending a request. Invalid configuration always raises
DiscoveryClientError(kind="configuration"); invalid request or selection
values raise DiscoveryClientError(kind="query"). Error messages never echo
caller values.
from registry_discovery_client import (
DiscoveryClient,
accept_selection,
select_evidence_alternative,
select_evidence_service,
validate_selection_structure,
)
from registry_evidence_client import EvidenceClient
# These values come from application-owned configuration or deployment
# ceremony. They are never copied from the Discovery response being checked.
evidence_pins = {
"serviceId": "urn:example:service:evidence",
"endpointUrl": "https://evidence.example.invalid/",
"publisherId": "urn:example:publisher",
"legalIssuerId": "urn:example:legal-issuer",
"technicalProviderId": "urn:example:technical-provider",
"jurisdictions": ["urn:example:jurisdiction"],
"conformsTo": ["urn:example:evidence-profile"],
"matchedCapability": {
"kind": "evidence-type",
"id": "urn:example:evidence-type",
},
"resolution": {
"requirementId": "urn:example:requirement",
"jurisdiction": "urn:example:jurisdiction",
"mappingRevision": (
"sha256:1111111111111111111111111111111111111111111111111111111111111111"
),
"evidenceTypeListId": "urn:example:evidence-list",
"evidenceTypeIds": ["urn:example:evidence-type"],
"mappingId": "urn:example:mapping",
"mappingAuthorityId": "urn:example:mapping-authority",
},
"originId": "approved-origin",
"originUrl": "https://publisher.example.invalid/catalog.jsonld",
}
def accepts_expected_evidence(candidate):
resolution = candidate.get("evidenceResolution") or {}
return (
candidate["serviceKind"] == "evidence"
and candidate["serviceId"] == evidence_pins["serviceId"]
and candidate["endpointUrl"] == evidence_pins["endpointUrl"]
and candidate.get("publisherId") == evidence_pins["publisherId"]
and candidate.get("legalIssuerId") == evidence_pins["legalIssuerId"]
and candidate.get("technicalProviderId")
== evidence_pins["technicalProviderId"]
and candidate["jurisdictions"] == evidence_pins["jurisdictions"]
and candidate["conformsTo"] == evidence_pins["conformsTo"]
and candidate["matchedCapability"] == evidence_pins["matchedCapability"]
and {
key: resolution.get(key)
for key in evidence_pins["resolution"]
}
== evidence_pins["resolution"]
and candidate["originId"] == evidence_pins["originId"]
and candidate["originUrl"] == evidence_pins["originUrl"]
)
client = DiscoveryClient("https://discovery.example.invalid/")
resolved = client.resolve_evidence_types({
"requirementId": "urn:example:requirement",
"jurisdiction": "urn:example:jurisdiction",
})
context = select_evidence_alternative(resolved) # refuses zero or many alternatives
for evidence_type_id in context["evidenceTypeIds"]:
services = client.search_evidence_services({
"evidenceTypeId": evidence_type_id,
"jurisdiction": context.get("jurisdiction"),
})
# The adopter chooses explicitly. Discovery supplies no catalog ranking.
matches = [
item for item in services["items"]
if item["serviceId"] == evidence_pins["serviceId"]
]
if len(matches) != 1:
raise ValueError(
"the locally expected Evidence service is unavailable or ambiguous"
)
chosen = matches[0]
selection = select_evidence_service(services, {
"recordId": chosen["recordId"],
"evidenceTypeId": evidence_type_id,
"resolution": context,
})
# Structural validation checks shape and capability binding. It does not
# establish origin authenticity, currentness, or trust.
checked = validate_selection_structure(selection)
accepted = accept_selection(checked, accepts_expected_evidence)
# Credentials and the native client are created only from the ephemeral
# accepted handoff. The values below are application-owned configuration.
evidence = EvidenceClient(
accepted.endpoint_url,
trusted_jwks,
revoked_key_ids,
token,
)
checked = accepted.selection
resolution = checked.get("evidenceResolution")
if resolution is None:
raise ValueError("missing Evidence resolution")
prepared = evidence.prepare({
**local_evidence_policy,
"requirement": resolution["requirementId"],
"evidence_type": checked["matchedCapability"]["id"],
})
verified = evidence.request_and_verify(prepared)
An Evidence alternative is an AND-list. The loop performs the search, explicit
choice, trust check, and native request for every context["evidenceTypeIds"]
member.
The context supplies the resolved requirement and selected Evidence Type. The
native definition and local policy still supply the purpose, audience,
issuer/provider identity, configuration revision, selectors, and expected
outputs.
validate_selection remains a compatibility alias, but its behavior has
always been structural. New code should use validate_selection_structure so
the result cannot be mistaken for a trust decision.
Persisted selections and renewal
Persist only the inert selection dictionary, never AcceptedServiceSelection.
Offline loading can establish structural validity, but cannot prove that the
catalog, mapping, endpoint, roles, or application policy are still current:
import json
persisted = validate_selection_structure(json.loads(saved_selection_json))
if application_selection_age_is_acceptable(persisted["originFetchedAt"]):
accepted = accept_selection(persisted, accepts_expected_evidence)
# Construct credentials and the native client only after this point.
The application owns the offline age limit. Discovery deliberately supplies no universal time-to-live.
For online renewal, resolve and search again, explicitly choose the same local
service, and build a fresh selection. renew_unchanged_selection accepts only
fetch-provenance and global catalog-revision changes:
from registry_discovery_client import renew_unchanged_selection
previous_resolution = persisted["evidenceResolution"]
fresh_resolved = client.resolve_evidence_types({
"requirementId": previous_resolution["requirementId"],
"jurisdiction": previous_resolution.get("jurisdiction"),
})
fresh_context = select_evidence_alternative(
fresh_resolved,
previous_resolution["evidenceTypeListId"],
)
evidence_type_id = persisted["matchedCapability"]["id"]
fresh_services = client.search_evidence_services({
"evidenceTypeId": evidence_type_id,
"jurisdiction": fresh_context.get("jurisdiction"),
})
fresh_matches = [
item for item in fresh_services["items"]
if item["serviceId"] == evidence_pins["serviceId"]
]
if len(fresh_matches) != 1:
raise ValueError(
"the previously selected service was withdrawn or is ambiguous"
)
fresh = select_evidence_service(fresh_services, {
"recordId": fresh_matches[0]["recordId"],
"evidenceTypeId": evidence_type_id,
"resolution": fresh_context,
})
renewed = renew_unchanged_selection(persisted, fresh)
accepted = accept_selection(renewed, accepts_expected_evidence)
A changed service identity, endpoint, issuer/provider, profile, jurisdiction,
capability, origin, or mapping context raises
DiscoveryClientError(kind="selection_changed"). A withdrawn record fails the
fresh selection. Both cases require explicit reselection and a new local
acceptance decision; renewal never switches to another service or Evidence
alternative automatically.
Relay follows the same boundary with search_relay_services and
select_relay_service. The selection retains both the semantic class and
operation family. Apply exact local Relay pins with accept_selection, pass
only accepted.endpoint_url to registry_relay_client.RelayClient, then use
native Relay metadata to choose the concrete resource and operation. Discovery
never invents route arguments.
Release files for registry-discovery-client 0.26.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| registry_discovery_client-0.26.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| registry_discovery_client-0.26.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| registry_discovery_client-0.26.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 5.1 MB
Release files / registry_discovery_client-0.26.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | registry_discovery_client-0.26.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
35fd60c3a33673afae735723f0dcfdc9b9ec041faf7cbce447e0284657f974be
|
|
BLAKE2b-256 checksum How to use checksums |
13b6defd078d80da658a25988c09f2bf18f91deaecf14ae96a3c2aa78daf96e4
|
| 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 4, 2026.
Transparency logRelease files / registry_discovery_client-0.26.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | registry_discovery_client-0.26.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
fccc0094b54ec30503453d09fab46e57801a5e6494accb523e14ffeb4f57f947
|
|
BLAKE2b-256 checksum How to use checksums |
bf68071498b31d85bef0fc9cd66adaa71a5e2a62fd9ad4816142e31c12419a97
|
| 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 4, 2026.
Transparency logRelease files / registry_discovery_client-0.26.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | registry_discovery_client-0.26.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
0bbf24a9c315b7231495f19900681eb6308b35af2e3f3c1d49fd04ed09f1d291
|
|
BLAKE2b-256 checksum How to use checksums |
0f002841a3535dffa5a2fad00aab489a3ba4bbafb8fb15b65e8d96430e4aafc5
|
| 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 4, 2026.
Transparency log