from __future__ import annotations from dataclasses import dataclass, field from typing import Any from .base import ExchangeBusinessSpec, ExchangeScope, ExchangeTaskHandler @dataclass class ExchangeRegistry: specs: dict[str, ExchangeBusinessSpec] = field(default_factory=dict) handlers: dict[str, ExchangeTaskHandler] = field(default_factory=dict) sources: dict[str, object] = field(default_factory=dict) def register_spec( self, spec: ExchangeBusinessSpec, *, handler: ExchangeTaskHandler | None = None, handler_name: str | None = None, ) -> ExchangeBusinessSpec: key = spec.scope.key() if key in self.specs: raise ValueError(f"exchange spec already registered: {key}") resolved_handler_name = handler_name or spec.handler_name or key if handler is not None: self.register_handler(resolved_handler_name, handler) spec = ExchangeBusinessSpec( scope=spec.scope, name=spec.name, description=spec.description, layout=spec.layout, variables=spec.variables, code=spec.code, handler_name=resolved_handler_name, ) self.specs[key] = spec return spec def get_spec( self, *, biz_domain: str, biz_obj: str, operation: str, ) -> ExchangeBusinessSpec | None: return self.specs.get( ExchangeScope.from_mapping( biz_domain=biz_domain, biz_obj=biz_obj, operation=operation, ).key() ) def list_specs(self) -> list[ExchangeBusinessSpec]: return sorted( self.specs.values(), key=lambda item: ( item.scope.biz_domain, item.scope.biz_obj, str(item.scope.operation), ), ) def register_handler( self, name: str, handler: ExchangeTaskHandler, ) -> ExchangeTaskHandler: if not name: raise ValueError("handler name is required") if name in self.handlers: raise ValueError(f"exchange handler already registered: {name}") self.handlers[name] = handler return handler def get_handler(self, name: str) -> ExchangeTaskHandler | None: return self.handlers.get(name) def get_scope_handler( self, *, biz_domain: str, biz_obj: str, operation: str, ) -> ExchangeTaskHandler | None: spec = self.get_spec( biz_domain=biz_domain, biz_obj=biz_obj, operation=operation, ) if spec is None: return None return self.handlers.get(spec.handler_name or spec.scope.key()) def catalog(self) -> list[dict[str, Any]]: domains: dict[str, dict[str, Any]] = {} for spec in self.list_specs(): domain = domains.setdefault( spec.scope.biz_domain, { "biz_domain": spec.scope.biz_domain, "objects": {}, }, ) obj = domain["objects"].setdefault( spec.scope.biz_obj, { "biz_obj": spec.scope.biz_obj, "operations": [], }, ) obj["operations"].append(spec.as_payload()) result: list[dict[str, Any]] = [] for domain in domains.values(): result.append( { "biz_domain": domain["biz_domain"], "objects": list(domain["objects"].values()), } ) return result def register_source(self, name: str, source: object) -> object: if not name: raise ValueError("source name is required") if name in self.sources: raise ValueError(f"exchange source already registered: {name}") self.sources[name] = source return source def get_source(self, name: str) -> object | None: return self.sources.get(name) def get_exchange_registry(app) -> ExchangeRegistry: registry = getattr(app.state, "iti_exchange", None) if registry is None: registry = ExchangeRegistry() app.state.iti_exchange = registry return registry def register_exchange_spec( app, spec: ExchangeBusinessSpec, *, handler: ExchangeTaskHandler | None = None, handler_name: str | None = None, ) -> ExchangeBusinessSpec: return get_exchange_registry(app).register_spec( spec, handler=handler, handler_name=handler_name, ) def register_exchange_handler( app, name: str, handler: ExchangeTaskHandler, ) -> ExchangeTaskHandler: return get_exchange_registry(app).register_handler(name, handler) def register_exchange_source(app, name: str, source: object) -> object: return get_exchange_registry(app).register_source(name, source) def get_exchange_source_by_name(app, name: str) -> object | None: return get_exchange_registry(app).get_source(name)