"""Source registry — selection + enumeration of configured backends. Built from settings at the composition root. Only sources that are configured are registered (e.g. ``local`` appears only when ``LOCAL_MEDIA_IMPORT_PATH`` is set), so enumeration reflects what the instance can actually use. """ from typing import cast from app.core.config import Settings from app.domain.errors import NotFoundError, ValidationError from app.domain.ports import IndexableSource, SourceBackend from app.domain.sources import SourceInfo from app.infrastructure.sources.local_folder import LocalFolderSource class SourceRegistry: def __init__(self, backends: list[SourceBackend]) -> None: self._by_name = {backend.name: backend for backend in backends} def get(self, name: str) -> SourceBackend: backend = self._by_name.get(name) if backend is None: raise NotFoundError(f"Source {name!r} is not configured.") return backend def indexable(self, name: str) -> IndexableSource: backend = self.get(name) if not hasattr(backend, "scan"): raise ValidationError(f"Source {name!r} cannot be indexed.") return cast(IndexableSource, backend) def infos(self) -> list[SourceInfo]: return [backend.info() for backend in self._by_name.values()] def build_source_registry(settings: Settings) -> SourceRegistry: backends: list[SourceBackend] = [] if settings.local_media_import_path is not None: backends.append(LocalFolderSource(settings.local_media_import_path)) return SourceRegistry(backends)