From 27df8c0a8dd5f52880ee93b481b558bccd72ddc2 Mon Sep 17 00:00:00 2001 From: Roberto Date: Sat, 16 May 2026 02:45:12 +0200 Subject: [PATCH] feat(scouts): add connector registry --- app/scouts/connectors/registry.py | 32 +++++++++++++++++ tests/test_scout_connector_registry.py | 48 ++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 app/scouts/connectors/registry.py create mode 100644 tests/test_scout_connector_registry.py diff --git a/app/scouts/connectors/registry.py b/app/scouts/connectors/registry.py new file mode 100644 index 0000000..a06bcb6 --- /dev/null +++ b/app/scouts/connectors/registry.py @@ -0,0 +1,32 @@ +"""Connector registry — single source of truth for source_type -> connector.""" + +from __future__ import annotations + +from typing import Any + +_CONNECTORS: dict[str, Any] = {} + + +def register_connector(connector: Any) -> None: + """Register a SourceConnector instance under its ``source_type``. + + Calling twice with the same ``source_type`` replaces the prior entry — + useful for tests and hot-reload, but in production each connector + should be registered exactly once at startup. + """ + if not getattr(connector, "source_type", None): + raise ValueError("Connector must declare a non-empty source_type") + _CONNECTORS[connector.source_type] = connector + + +def get_connector(source_type: str) -> Any: + """Return the registered connector for ``source_type`` or raise KeyError.""" + try: + return _CONNECTORS[source_type] + except KeyError as exc: + raise KeyError(f"No connector registered for source_type {source_type!r}") from exc + + +def _reset_for_tests() -> None: + """Clear the registry — for use in pytest fixtures only.""" + _CONNECTORS.clear() diff --git a/tests/test_scout_connector_registry.py b/tests/test_scout_connector_registry.py new file mode 100644 index 0000000..038c7b2 --- /dev/null +++ b/tests/test_scout_connector_registry.py @@ -0,0 +1,48 @@ +"""Tests for the connector registry.""" + +from __future__ import annotations + +import pytest + +from app.scouts.connectors.base import ItemRef +from app.scouts.connectors.registry import ( + get_connector, + register_connector, + _reset_for_tests, +) + + +class _DummyConnector: + source_type = "dummy" + async def list_new(self, scout): return [] + async def fetch_metadata(self, scout, ref): raise NotImplementedError + async def fetch_content(self, scout, ref): raise NotImplementedError + async def archive(self, scout, ref): raise NotImplementedError + async def setup_watch(self, scout): raise NotImplementedError + async def renew_watch(self, scout): raise NotImplementedError + + +@pytest.fixture(autouse=True) +def _clean_registry(): + _reset_for_tests() + yield + _reset_for_tests() + + +def test_register_and_get(): + c = _DummyConnector() + register_connector(c) + assert get_connector("dummy") is c + + +def test_unknown_source_raises(): + with pytest.raises(KeyError): + get_connector("nope") + + +def test_double_register_replaces(): + a = _DummyConnector() + b = _DummyConnector() + register_connector(a) + register_connector(b) + assert get_connector("dummy") is b