refactor: remove TomlConfigSource, keep only DbConfigSource

TOML config is now retired. Database is the sole configuration source.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ubuntu 2026-05-16 03:42:28 +00:00
commit 4376588baf

View file

@ -1,187 +1,80 @@
"""Configuration source abstraction. """Configuration source abstraction.
Provides a unified interface for loading adapter configuration from Provides a unified interface for loading adapter configuration from
either TOML files or the database-backed config store. the database-backed config store.
""" """
import logging import logging
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from pathlib import Path from typing import Protocol, runtime_checkable
from typing import Any, Protocol, runtime_checkable
from central.config_models import AdapterConfig
import tomllib from central.config_store import ConfigStore
from central.config import NWSAdapterConfig logger = logging.getLogger(__name__)
from central.config_models import AdapterConfig
from central.config_store import ConfigStore
@runtime_checkable
logger = logging.getLogger(__name__) class ConfigSource(Protocol):
"""Protocol for configuration sources."""
@runtime_checkable async def list_enabled_adapters(self) -> list[AdapterConfig]:
class ConfigSource(Protocol): """List all enabled adapters."""
"""Protocol for configuration sources.""" ...
async def list_enabled_adapters(self) -> list[AdapterConfig]: async def get_adapter(self, name: str) -> AdapterConfig | None:
"""List all enabled adapters.""" """Get configuration for a specific adapter."""
... ...
async def get_adapter(self, name: str) -> AdapterConfig | None: async def watch_for_changes(
"""Get configuration for a specific adapter.""" self,
... callback: Callable[[str, str], Awaitable[None] | None],
) -> None:
async def watch_for_changes( """Watch for configuration changes.
self,
callback: Callable[[str, str], Awaitable[None] | None], Runs forever, calling callback(table, key) on changes.
) -> None: """
"""Watch for configuration changes. ...
For TOML source, this is a no-op (returns immediately). async def close(self) -> None:
For DB source, this runs forever, calling callback(table, key) on changes. """Clean up resources."""
""" ...
...
async def close(self) -> None: class DbConfigSource:
"""Clean up resources.""" """Configuration source backed by the Postgres config store.
...
Supports hot-reload via LISTEN/NOTIFY.
"""
class TomlConfigSource:
"""Configuration source backed by a TOML file. def __init__(self, config_store: ConfigStore) -> None:
self._store = config_store
This is the legacy configuration path. Does not support hot-reload.
""" @classmethod
async def create(cls, dsn: str) -> "DbConfigSource":
def __init__(self, toml_path: Path) -> None: """Create a DbConfigSource with a new ConfigStore."""
self._toml_path = toml_path store = await ConfigStore.create(dsn)
self._adapters: dict[str, AdapterConfig] = {} return cls(store)
self._loaded = False
async def list_enabled_adapters(self) -> list[AdapterConfig]:
def _load(self) -> None: """List all enabled adapters from database."""
"""Load configuration from TOML file.""" all_adapters = await self._store.list_adapters()
if self._loaded: return [a for a in all_adapters if a.enabled and not a.is_paused]
return
async def get_adapter(self, name: str) -> AdapterConfig | None:
with self._toml_path.open("rb") as f: """Get a specific adapter from database."""
data = tomllib.load(f) return await self._store.get_adapter(name)
adapters_raw = data.get("adapters", {}) async def watch_for_changes(
from datetime import datetime, timezone self,
callback: Callable[[str, str], Awaitable[None] | None],
now = datetime.now(timezone.utc) ) -> None:
"""Watch for changes via Postgres LISTEN/NOTIFY.
for name, adapter_data in adapters_raw.items():
# Convert TOML adapter config to unified AdapterConfig Runs forever, calling callback(table, key) on each change.
# TOML uses NWSAdapterConfig shape, we need to convert to AdapterConfig """
enabled = adapter_data.get("enabled", True) await self._store.listen_for_changes(callback)
cadence_s = adapter_data.get("cadence_s", 60)
async def close(self) -> None:
# Extract settings (everything except enabled/cadence_s) """Close the underlying config store."""
settings = { await self._store.close()
k: v
for k, v in adapter_data.items()
if k not in ("enabled", "cadence_s")
}
self._adapters[name] = AdapterConfig(
name=name,
enabled=enabled,
cadence_s=cadence_s,
settings=settings,
paused_at=None,
updated_at=now,
)
self._loaded = True
logger.info(
"Loaded TOML config",
extra={"path": str(self._toml_path), "adapters": list(self._adapters.keys())},
)
async def list_enabled_adapters(self) -> list[AdapterConfig]:
"""List all enabled adapters from TOML."""
self._load()
return [a for a in self._adapters.values() if a.enabled and not a.is_paused]
async def get_adapter(self, name: str) -> AdapterConfig | None:
"""Get a specific adapter from TOML."""
self._load()
return self._adapters.get(name)
async def watch_for_changes(
self,
callback: Callable[[str, str], Awaitable[None] | None],
) -> None:
"""TOML does not support hot-reload. Returns immediately."""
logger.debug("TOML config source does not support hot-reload")
return
async def close(self) -> None:
"""No resources to clean up for TOML source."""
pass
class DbConfigSource:
"""Configuration source backed by the Postgres config store.
Supports hot-reload via LISTEN/NOTIFY.
"""
def __init__(self, config_store: ConfigStore) -> None:
self._store = config_store
@classmethod
async def create(cls, dsn: str) -> "DbConfigSource":
"""Create a DbConfigSource with a new ConfigStore."""
store = await ConfigStore.create(dsn)
return cls(store)
async def list_enabled_adapters(self) -> list[AdapterConfig]:
"""List all enabled adapters from database."""
all_adapters = await self._store.list_adapters()
return [a for a in all_adapters if a.enabled and not a.is_paused]
async def get_adapter(self, name: str) -> AdapterConfig | None:
"""Get a specific adapter from database."""
return await self._store.get_adapter(name)
async def watch_for_changes(
self,
callback: Callable[[str, str], Awaitable[None] | None],
) -> None:
"""Watch for changes via Postgres LISTEN/NOTIFY.
Runs forever, calling callback(table, key) on each change.
"""
await self._store.listen_for_changes(callback)
async def close(self) -> None:
"""Close the underlying config store."""
await self._store.close()
async def create_config_source(
source_type: str,
dsn: str | None = None,
toml_path: Path | None = None,
) -> ConfigSource:
"""Factory function to create the appropriate config source.
Args:
source_type: "toml" or "db"
dsn: PostgreSQL DSN (required for "db")
toml_path: Path to TOML file (required for "toml")
Returns:
ConfigSource implementation
"""
if source_type == "toml":
if toml_path is None:
raise ValueError("toml_path required for toml config source")
return TomlConfigSource(toml_path)
elif source_type == "db":
if dsn is None:
raise ValueError("dsn required for db config source")
return await DbConfigSource.create(dsn)
else:
raise ValueError(f"Unknown config source type: {source_type}")