from __future__ import annotations import json import os from dataclasses import dataclass from pathlib import Path from typing import Any, Mapping from backend.bootstrap.config import APP_DIR DATASETS = ( "calendar", "stocks", "daily", "index_daily", "valuation", "moneyflow", "auction", "status", ) ENV_DATASET = { "calendar": "CALENDAR", "stocks": "STOCKS", "daily": "DAILY", "index_daily": "INDEX_DAILY", "valuation": "VALUATION", "moneyflow": "MONEYFLOW", "auction": "AUCTION", "status": "STATUS", } DEFAULT_CONFIG_PATH = APP_DIR / "config" / "datahub.config.json" def _truthy(value: Any) -> bool: return str(value or "").strip().lower() in {"1", "true", "yes", "on"} def _int(value: Any, default: int) -> int: try: return int(value) except (TypeError, ValueError): return default @dataclass(frozen=True) class DatasetFlags: name: str read: bool = False shadow: bool = False @dataclass(frozen=True) class DatahubSettings: base_url: str token: str timeout_seconds: int = 8 retries: int = 1 page_limit: int = 5000 stale_seconds_max: int = 86400 datasets: dict[str, DatasetFlags] | None = None def flags(self, dataset: str) -> DatasetFlags: mapped = self.datasets or {} return mapped.get(dataset) or DatasetFlags(dataset) def any_enabled(self) -> bool: return any(item.read or item.shadow for item in (self.datasets or {}).values()) def secrets(self) -> tuple[str, ...]: return tuple(item for item in (self.token,) if item) @classmethod def load( cls, path: Path | None = None, environ: Mapping[str, str] | None = None, credentials: Mapping[str, object] | None = None, ) -> "DatahubSettings": config_path = path or DEFAULT_CONFIG_PATH payload: dict[str, Any] = {} if config_path.is_file(): payload = json.loads(config_path.read_text(encoding="utf-8")) env = dict(os.environ if environ is None else environ) creds = dict(credentials or {}) dataset_flags: dict[str, DatasetFlags] = {} raw_datasets = payload.get("datasets") or {} for name in DATASETS: item = raw_datasets.get(name) or {} env_key = ENV_DATASET[name] read = _truthy(env.get(f"DATAHUB_READ_{env_key}")) if f"DATAHUB_READ_{env_key}" in env else bool(item.get("read")) shadow = ( _truthy(env.get(f"DATAHUB_SHADOW_{env_key}")) if f"DATAHUB_SHADOW_{env_key}" in env else bool(item.get("shadow")) ) dataset_flags[name] = DatasetFlags(name, read=read, shadow=shadow) token = str( env.get("DATAHUB_TOKEN") or creds.get("datahub_token") or payload.get("token") or "" ).strip() base_url = str( env.get("DATAHUB_BASE_URL") or creds.get("datahub_base_url") or payload.get("base_url") or "http://127.0.0.1:8766" ).strip().rstrip("/") return cls( base_url=base_url, token=token, timeout_seconds=_int(env.get("DATAHUB_TIMEOUT") or payload.get("timeout_seconds"), 8), retries=max(0, _int(env.get("DATAHUB_RETRIES") or payload.get("retries"), 1)), page_limit=max(1, _int(payload.get("page_limit"), 5000)), stale_seconds_max=max(0, _int(payload.get("stale_seconds_max"), 86400)), datasets=dataset_flags, )