"""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()