Contract Store¶
The contract store provides centralized schema registry functionality. Contract identity is namespace, contract name, and contract version.
Overview¶
from pycharter import PostgresContractStore
store = PostgresContractStore("postgresql://...")
store.connect()
# Store and retrieve by contract name and version
store.store_json_schema("user", "1.0.0", schema)
store.store_metadata("user", "1.0.0", metadata)
schema = store.get_json_schema("user", "1.0.0")
contracts = store.list_contracts() # [{"name": "user", "version": "1.0.0"}, ...]
store.disconnect()
Namespace and latest-version resolution¶
Pass namespace="market" to select a namespace explicitly. Unqualified
latest_version(name) and get(name) raise AmbiguousContractError when the
name appears in multiple namespaces, even when only one namespace has an
active version. Empty and whitespace namespace arguments remain unqualified.
An explicit version may resolve unqualified when that (name, version) exists
in exactly one namespace; specify the namespace if that pair is ambiguous.
Within one resolved namespace, latest_version() prefers the newest version
whose status is neither deprecated nor retired. If all versions have those
statuses, it returns the absolute latest version for compatibility. Use an
explicit version when a reference must remain pinned.
Version precedence and selection ties¶
Latest selection uses SemVer precedence:
numeric dotted prerelease identifiers compare numerically (rc.10 > rc.2),
numeric identifiers precede nonnumeric ones, and a release follows its
prereleases. Build metadata is accepted but does not affect precedence.
When two stored spellings have equal precedence, the lexicographically greatest
original version string wins selection, independent of insertion/backend order.
For example, 1.0.0+build.z is selected over 1.0.0+build.a, but is not a
semantically newer version. Exact pins retain their original identity.
compare_versions() and is_version_higher() compare precedence only.
get_latest_version() and version_sort_key() apply the documented textual
selection tie-break. Proposal admission uses precedence, so changing only build
metadata or a legacy spelling cannot satisfy a required version increase.
The compatibility helpers remain tolerant: 1, 1.2, leading v/V, and
previously accepted legacy spellings still parse. They do not replace native
artifact SemVer validation. parse_version() retains its four-component
return value and omits build metadata. The public sort key remains a six-tuple
of five integers and a string; its final string is now an opaque sortable
encoding rather than a prerelease label. Do not persist or interpret that
string as an artifact version. Unparseable strings still sort before parseable
versions and lexically among themselves. Comparison still raises on invalid
versions; get_latest_version() retains its empty/singleton behavior and raises
on invalid entries when comparing multiple versions.
The same helper serves pipeline stores, semantic scheme/document lookups, agent preparation, and native DAG references. Their lifecycle policies are unchanged: contract latest prefers non-deprecated/non-retired versions, preparation requires active versions, and a DAG's unpinned reference selects the absolute latest known version. Namespace ambiguity remains an error before contract lifecycle or version filtering. Semantic-store acceptance checks ordering of existing versioned rows; semantic mutation authority remains a separate backlog item.
Store Implementations¶
| Class | Backend | Use Case |
|---|---|---|
InMemoryContractStore |
Memory | Testing |
SQLiteContractStore |
SQLite | Development |
PostgresContractStore |
PostgreSQL | Production |
MongoDBContractStore |
MongoDB | Document-oriented |
RedisContractStore |
Redis | Caching |
ContractStoreClient¶
Base interface for all stores:
ContractStoreClient
¶
Bases: VersionedArtifactStore
Client for storing and retrieving versioned data contracts.
All operations are keyed by (contract_name, contract_version).
Implementations extend this for specific backends (PostgreSQL, SQLite,
MongoDB, Redis, InMemory).
The shared :class:~pycharter.stores.base.VersionedArtifactStore
surface (save / get / list / list_versions /
set_status / update / delete) is the single public write
path. save writes the whole contract atomically; update
applies a sectional patch. The contract document carries::
{json_schema, coercion_rules, validation_rules, metadata,
field_bindings, data_quality, sla_default_element, sla_properties}
json_schema is required on :meth:save; the rest are optional.
The per-artifact getters (get_json_schema, get_metadata,
get_coercion_rules, get_validation_rules, get_field_bindings,
get_data_quality, get_sla, get_complete_json_schema,
get_contract) remain public; the per-artifact writers are
internal to each backend's atomic write path.
NS-2/NS-3 — the full identity is (namespace, name, version)
where namespace=None (or "") means the global namespace.
Identity-based reads and writes accept a keyword-only
namespace:
namespacegiven: exact(namespace, name, version)match.namespaceomitted: match by(name, version); when the name exists in exactly one namespace it resolves as before, and when several namespaces hold it the lookup raises :class:~pycharter.stores.errors.AmbiguousContractErrorlisting the candidates.
On :meth:save the namespace persists from the explicit keyword,
else from the document's metadata.namespace, else the global
namespace. The stored metadata payload keeps its namespace key
(the document is the source of truth; the stored identity mirrors
it).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection_string
|
str | None
|
Database connection string (format depends on implementation). |
None
|
store_json_schema
abstractmethod
¶
store_json_schema(
contract_name: str,
contract_version: str,
schema: dict[str, Any],
*,
namespace: str | None = None,
) -> str
Store a JSON Schema for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
schema
|
dict[str, Any]
|
JSON Schema dictionary (may contain "version" for schema artifact version). |
required |
namespace
|
str | None
|
Contract namespace; a newly created contract is
placed in it ( |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Contract ID or identifier (implementation-defined). |
get_json_schema
abstractmethod
¶
get_json_schema(
contract_name: str,
contract_version: str,
*,
namespace: str | None = None,
) -> dict[str, Any]
Retrieve the schema for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Schema dictionary with version included, or None if not found. |
list_contracts
abstractmethod
¶
List all stored data contracts.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
List of dicts with at least "name" and "version"; may include id, schema_id, etc. |
store_coercion_rules
abstractmethod
¶
store_coercion_rules(
contract_name: str,
contract_version: str,
coercion_rules: dict[str, Any],
*,
namespace: str | None = None,
) -> str
Store coercion rules for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
coercion_rules
|
dict[str, Any]
|
Dict mapping field names to coercion function names. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Coercion rules ID or identifier. |
get_coercion_rules
abstractmethod
¶
get_coercion_rules(
contract_name: str,
contract_version: str,
*,
namespace: str | None = None,
) -> dict[str, Any]
Retrieve coercion rules for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Coercion rules dictionary or None if not found. |
store_validation_rules
abstractmethod
¶
store_validation_rules(
contract_name: str,
contract_version: str,
validation_rules: dict[str, Any],
*,
namespace: str | None = None,
) -> str
Store validation rules for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
validation_rules
|
dict[str, Any]
|
Dict mapping field names to validation configs. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Validation rules ID or identifier. |
get_validation_rules
abstractmethod
¶
get_validation_rules(
contract_name: str,
contract_version: str,
*,
namespace: str | None = None,
) -> dict[str, Any]
Retrieve validation rules for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Validation rules dictionary or None if not found. |
store_metadata
abstractmethod
¶
store_metadata(
contract_name: str,
contract_version: str,
metadata: dict[str, Any],
*,
namespace: str | None = None,
) -> str
Store metadata for the given data contract.
NS-2: the document is the source of truth — a payload carrying
namespace stamps a contract still in the global namespace.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
metadata
|
dict[str, Any]
|
Metadata dictionary. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Metadata record ID or identifier. |
get_metadata
abstractmethod
¶
get_metadata(
contract_name: str,
contract_version: str,
*,
namespace: str | None = None,
) -> dict[str, Any]
Retrieve metadata for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Metadata dictionary or None if not found. |
get_contract
¶
get_contract(
contract_name: str,
contract_version: str,
include_metadata: bool = True,
include_ownership: bool = True,
include_governance: bool = True,
*,
namespace: str | None = None,
) -> dict[str, Any]
Return the full data contract (schema + coercion_rules + validation_rules + metadata) for the given contract name and version.
Default implementation assembles from get_json_schema, get_coercion_rules, get_validation_rules, get_metadata. Subclasses may override for efficiency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
include_metadata
|
bool
|
Whether to include metadata in the contract. |
True
|
include_ownership
|
bool
|
Whether to include ownership from metadata. |
True
|
include_governance
|
bool
|
Whether to include governance_rules from metadata. |
True
|
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Consolidated contract dict (schema, coercion_rules, validation_rules, |
dict[str, Any]
|
metadata, versions) or None if contract/schema not found. |
get_field_bindings
¶
get_field_bindings(
contract_name: str,
contract_version: str,
*,
namespace: str | None = None,
) -> dict[str, Any] | None
Retrieve field bindings artifact for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
Field-bindings artifact dictionary or None if not found. |
store_field_bindings
¶
store_field_bindings(
contract_name: str,
contract_version: str,
field_bindings: dict[str, Any],
*,
namespace: str | None = None,
) -> str
Store a field bindings artifact for the given data contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contract_name
|
str
|
Data contract name. |
required |
contract_version
|
str
|
Data contract version. |
required |
field_bindings
|
dict[str, Any]
|
Field bindings artifact payload. |
required |
namespace
|
str | None
|
Exact namespace, or |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Identifier for the stored field bindings. |
SQLiteContractStore¶
from pycharter import SQLiteContractStore
store = SQLiteContractStore("metadata.db")
store.connect()
# Store and retrieve by contract name and version
store.store_json_schema(
"user", "1.0.0", {"type": "object", "properties": {"name": {"type": "string"}}}
)
schema = store.get_json_schema("user", "1.0.0")
Existing SQLite databases: run pycharter db upgrade
Earlier releases declared UUID columns as UUID on SQLite, a type SQLite
treats as numeric. Roughly one UUID in a million has all-digit hex, and
SQLite stored those as floating-point numbers: the digits were lost and
the row could no longer be read. Databases created now are not affected.
Run pycharter db upgrade <database-url> once on an existing SQLite
database. The migration retypes the UUID columns to CHAR(32) without
rewriting data, and reports any value that was already corrupted (it
cannot be recovered). This also works for a database created by
connect(auto_initialize=True), which it brings under migrations, adding
the rest of pycharter's tables. PostgreSQL uses a native uuid type and
is unaffected.
PostgresContractStore¶
from pycharter import PostgresContractStore
store = PostgresContractStore("postgresql://user:pass@localhost/pycharter")
store.connect()
MongoDBContractStore¶
from pycharter import MongoDBContractStore
store = MongoDBContractStore("mongodb://localhost:27017/pycharter")
store.connect()
RedisContractStore¶
from pycharter import RedisContractStore
store = RedisContractStore("redis://localhost:6379/0")
store.connect()
InMemoryContractStore¶
from pycharter import InMemoryContractStore
store = InMemoryContractStore()
store.connect()
# Data is lost when program exits
Examples¶
Schema Versioning¶
# Store versions
store.store_json_schema("user", "1.0.0", schema_v1)
store.store_json_schema("user", "2.0.0", schema_v2)
# Get specific version
schema = store.get_json_schema("user", "1.0.0")
# List contracts
contracts = store.list_contracts()
A version is immutable where it decides a verdict: changing its schema,
coercion or validation rules, field bindings, data_quality.rules or
strict_types raises ArtifactContentConflictError, so publish a new
version instead. See
Artifact format versioning.
Storing Rules¶
# Coercion rules
store.store_coercion_rules(
"user", "1.0.0", {"version": "1.0.0", "rules": {"age": "coerce_to_integer"}}
)
# Validation rules
store.store_validation_rules(
"user", "1.0.0", {"version": "1.0.0", "rules": {"age": {"is_positive": {}}}}
)
Metadata¶
# Store metadata
store.store_metadata(
"user",
"1.0.0",
{"title": "User Schema", "owner": "data-team", "tags": ["user", "pii"]},
)
# Retrieve
metadata = store.get_metadata("user", "1.0.0")
Field bindings artifact¶
Stores that support semantic bindings (e.g. PostgresContractStore, InMemoryContractStore) persist the versioned field bindings artifact per contract.
# Store field bindings for a contract
store.store_field_bindings(
"user",
"1.0.0",
{
"version": "1.0.0",
"domain_schema": "trading-domain:1.0.0",
"field_bindings": {
"$.email": {
"primary": {
"concept": {"concept": "user_email"},
"concept_scheme": "trading-domain:1.0.0",
}
}
},
},
)
# Retrieve
field_bindings = store.get_field_bindings("user", "1.0.0")
When you call store.get_contract(name, version), the returned contract dict includes field_bindings when available.
Breaking change: legacy keys field_mapping, ontology (contract artifact), and store methods get_field_mapping / store_field_mapping were removed. Run pycharter db upgrade to rename field_mappings → field_bindings in PostgreSQL.