94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
"""Channel connector registry."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Protocol
|
|
|
|
from beaver.foundation.config.schema import ChannelConfig
|
|
|
|
from .models import ChannelRuntimeSpec, ValidationResult
|
|
from .store import ChannelConnectionStore, CredentialStore
|
|
|
|
|
|
class ChannelConnector(Protocol):
|
|
kind: str
|
|
|
|
async def validate(self, connection_id: str) -> ValidationResult:
|
|
...
|
|
|
|
async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec:
|
|
...
|
|
|
|
async def revoke(self, connection_id: str) -> None:
|
|
...
|
|
|
|
|
|
class ChannelConnectorRegistry:
|
|
def __init__(self, *, connection_store: ChannelConnectionStore, credential_store: CredentialStore) -> None:
|
|
self.connection_store = connection_store
|
|
self.credential_store = credential_store
|
|
self._connectors: dict[str, ChannelConnector] = {}
|
|
|
|
def register(self, connector: ChannelConnector) -> None:
|
|
kind = connector.kind.strip()
|
|
if not kind:
|
|
raise ValueError("Connector kind is required")
|
|
if kind in self._connectors:
|
|
raise ValueError(f"Connector already registered: {kind}")
|
|
self._connectors[kind] = connector
|
|
|
|
def connectors(self) -> list[dict[str, str]]:
|
|
return [{"kind": kind} for kind in sorted(self._connectors)]
|
|
|
|
def connector_for_kind(self, kind: str) -> ChannelConnector:
|
|
return self._connector(kind)
|
|
|
|
async def validate(self, connection_id: str) -> ValidationResult:
|
|
connection = self.connection_store.get(connection_id)
|
|
connector = self._connector(connection.kind)
|
|
result = await connector.validate(connection_id)
|
|
self.connection_store.update_status(
|
|
connection_id,
|
|
status=result.status,
|
|
last_error=result.error,
|
|
)
|
|
return result
|
|
|
|
async def materialize_runtime(self, connection_id: str) -> ChannelRuntimeSpec:
|
|
connection = self.connection_store.get(connection_id)
|
|
return await self._connector(connection.kind).materialize_runtime(connection_id)
|
|
|
|
async def materialize_connected_runtime_specs(self) -> list[ChannelRuntimeSpec]:
|
|
specs: list[ChannelRuntimeSpec] = []
|
|
for connection in self.connection_store.list():
|
|
if connection.status not in {"connected", "running"}:
|
|
continue
|
|
specs.append(await self._connector(connection.kind).materialize_runtime(connection.connection_id))
|
|
return specs
|
|
|
|
async def materialize_channel_configs(self) -> dict[str, ChannelConfig]:
|
|
channels: dict[str, ChannelConfig] = {}
|
|
for spec in await self.materialize_connected_runtime_specs():
|
|
secrets = self.credential_store.get(spec.secrets_ref) if spec.secrets_ref else {}
|
|
channels[spec.channel_id] = ChannelConfig(
|
|
enabled=True,
|
|
kind=spec.kind,
|
|
mode=spec.mode,
|
|
account_id=spec.account_id,
|
|
display_name=spec.display_name,
|
|
config=dict(spec.config),
|
|
secrets=secrets,
|
|
)
|
|
return channels
|
|
|
|
async def revoke(self, connection_id: str) -> None:
|
|
connection = self.connection_store.get(connection_id)
|
|
await self._connector(connection.kind).revoke(connection_id)
|
|
self.connection_store.revoke(connection_id)
|
|
|
|
def _connector(self, kind: str) -> ChannelConnector:
|
|
connector = self._connectors.get(kind)
|
|
if connector is None:
|
|
raise KeyError(f"Connector not registered: {kind}")
|
|
return connector
|