# ----------------------------------------------------------------------------- # Home-Ka — US real-estate aggregator (Groupe KA) # Author: Simon-Pierre Boucher — contact@spboucher.ai # connectors/__init__.py : AUTO-DISCOVERING connector registry (recursive). # # Same principle as immo-ka, extended with connector FAMILIES: # - FAMILIES: {"reso": RESOConnector, "xml": XMLFeedConnector, ...} # generic config-driven classes (class attribute `family`), instantiated # per source from the `sources` table — a new source of a known family # is a DB row, zero code. # - CUSTOM: {"acme_realty": AcmeRealtyConnector, ...} # one-site classes (class attribute `source_id`), like immo-ka. # Every module under this package (subpackages included) is imported; a broken # connector never blocks the others. # ----------------------------------------------------------------------------- from __future__ import annotations import importlib import pkgutil import sys from .base import BaseConnector FAMILIES: dict[str, type[BaseConnector]] = {} CUSTOM: dict[str, type[BaseConnector]] = {} def _register(module) -> None: for obj in vars(module).values(): if not (isinstance(obj, type) and issubclass(obj, BaseConnector) and obj is not BaseConnector): continue fam = getattr(obj, "family", "") sid = getattr(obj, "source_id", "") if fam and obj.__dict__.get("family"): FAMILIES[fam] = obj if sid and obj.__dict__.get("source_id"): CUSTOM[sid] = obj def _walk(package_name: str, path) -> None: for mod in pkgutil.iter_modules(path): if mod.name in ("base", "__init__"): continue full = f"{package_name}.{mod.name}" try: module = importlib.import_module(full) except Exception as exc: # a broken connector never blocks the others print(f"[home-ka] connector '{full}' skipped: {exc}", file=sys.stderr) continue _register(module) if mod.ispkg: _walk(full, module.__path__) _walk(__name__, __path__) def build(source_id: str, connector_type: str, config: dict | None = None) -> BaseConnector | None: """Instantiate the connector for a `sources` row (or a custom class).""" if source_id in CUSTOM: return CUSTOM[source_id]() cls = FAMILIES.get(connector_type) if cls is None: return None return cls(source_id, config or {})