1"""Base source abstraction for data adapters.""" 2 3from __future__ import annotations 4 5from abc import ABC, abstractmethod 6from typing import Any 7 8 9class BaseSource(ABC): 10 """Abstract base for all data source adapters. 11 12 Each adapter wraps a specific data provider (QuestDB, fomo2 stream, 13 web search, etc.) and returns structured data for agent consumption. 14 """ 15 16 adapter_name: str = "" 17 18 def __init__(self, config: dict[str, Any]) -> None: 19 self.config = config 20 21 @abstractmethod 22 async def fetch(self, params: dict[str, Any] | None = None) -> dict[str, Any]: 23 """Fetch data from this source. 24 25 Parameters 26 ---------- 27 params: 28 Runtime parameters that override/extend the static config. 29 E.g. {"symbols": ["AAPL"], "start": "2024-01-01"}. 30 31 Returns 32 ------- 33 Dict with the fetched data. Structure depends on adapter type. 34 """ 35 ... 36 37 async def check_health(self) -> bool: 38 """Check if this source is available.""" 39 try: 40 await self.fetch() 41 return True 42 except Exception: 43 return False 44 45 @classmethod 46 def freshness_spec(cls, binding_config: dict[str, Any]) -> dict[str, Any]: 47 """Declare where this adapter's data lives so the dashboard can 48 render a "live · stale · empty" badge next to each binding. 49 50 Subclasses override this to point at the Redis stream/key they 51 consume. The default ``{"type": "unknown"}`` keeps the dashboard 52 honest — badges will say "unknown" rather than fabricating an age. 53 54 Supported types: 55 * ``stream`` — single Redis Stream; report XLEN + last-id age. 56 Required key: ``stream`` (str). 57 * ``key`` — one or more Redis string keys (TTL'd cache). 58 Required key: ``key_pattern`` (str, may contain {placeholders}). 59 Optional: ``emit_stream`` (str) — gives an accurate "last refreshed" age. 60 * ``scan`` — pattern scan only; reports key count. 61 Required key: ``scan_pattern``. 62 Optional: ``emit_stream``. 63 * ``external`` — external HTTP/SQL, no caching. 64 Optional key: ``detail`` (str) — shown in tooltip. 65 * ``request_response`` — round-trips through a Redis Stream. 66 Required key: ``out_stream``. 67 * ``unknown`` — default; dashboard renders "unknown". 68 69 Placeholders in ``key_pattern`` / ``stream`` / ``scan_pattern`` are 70 resolved against ``binding_config`` (e.g. ``"trtools2:bars:{timeframe}"``). 71 72 Implementations must be cheap — this runs on every Setup-tab poll. 73 """ 74 return {"type": "unknown"} 75 76 def __repr__(self) -> str: 77 return f"<{self.__class__.__name__} adapter={self.adapter_name!r}>"