97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
"""Graph-of-Contracts registry for versioned adapter/schema metadata."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from typing import Dict, Iterable, List, Optional, Tuple
|
|
|
|
from energi_bridge.schemas import RegistryMetadata
|
|
|
|
|
|
def _parse_version(version: str) -> Tuple[int, int, int, str]:
|
|
core, _, suffix = version.partition("-")
|
|
parts = core.split(".")
|
|
while len(parts) < 3:
|
|
parts.append("0")
|
|
major, minor, patch = (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
return major, minor, patch, suffix
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ContractDefinition:
|
|
contract_id: str
|
|
version: str
|
|
adapter_id: str
|
|
schema_name: str
|
|
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
replay_hint: Optional[str] = None
|
|
previous_version: Optional[str] = None
|
|
|
|
def to_metadata(self) -> RegistryMetadata:
|
|
return RegistryMetadata(
|
|
contract_version=self.version,
|
|
adapter_id=self.adapter_id,
|
|
contract_id=self.contract_id,
|
|
schema_name=self.schema_name,
|
|
created_at=self.created_at,
|
|
replay_hint=self.replay_hint,
|
|
)
|
|
|
|
|
|
class ContractRegistry:
|
|
"""Maintain contract versions for schemas and adapters."""
|
|
|
|
def __init__(self) -> None:
|
|
self._contracts: Dict[str, Dict[str, ContractDefinition]] = {}
|
|
|
|
def register(
|
|
self,
|
|
contract_id: str,
|
|
version: str,
|
|
adapter_id: str,
|
|
schema_name: str,
|
|
replay_hint: Optional[str] = None,
|
|
) -> ContractDefinition:
|
|
versions = self._contracts.setdefault(contract_id, {})
|
|
if version in versions:
|
|
raise ValueError(f"Contract {contract_id}@{version} already exists")
|
|
|
|
previous_version = self.latest_version(contract_id)
|
|
definition = ContractDefinition(
|
|
contract_id=contract_id,
|
|
version=version,
|
|
adapter_id=adapter_id,
|
|
schema_name=schema_name,
|
|
replay_hint=replay_hint,
|
|
previous_version=previous_version,
|
|
)
|
|
versions[version] = definition
|
|
return definition
|
|
|
|
def latest_version(self, contract_id: str) -> Optional[str]:
|
|
versions = self._contracts.get(contract_id, {})
|
|
if not versions:
|
|
return None
|
|
return max(versions, key=_parse_version)
|
|
|
|
def resolve(self, contract_id: str, version: Optional[str] = None) -> ContractDefinition:
|
|
versions = self._contracts.get(contract_id, {})
|
|
if not versions:
|
|
raise KeyError(contract_id)
|
|
selected = version or self.latest_version(contract_id)
|
|
if selected is None or selected not in versions:
|
|
raise KeyError(f"Unknown contract version: {contract_id}@{version}")
|
|
return versions[selected]
|
|
|
|
def manifest(self) -> List[RegistryMetadata]:
|
|
metadata: List[RegistryMetadata] = []
|
|
for contract_versions in self._contracts.values():
|
|
for definition in contract_versions.values():
|
|
metadata.append(definition.to_metadata())
|
|
return sorted(metadata, key=lambda item: (item.contract_id or "", _parse_version(item.contract_version)))
|
|
|
|
def items(self) -> Iterable[ContractDefinition]:
|
|
for contract_versions in self._contracts.values():
|
|
yield from contract_versions.values()
|