from __future__ import annotations from dataclasses import dataclass from datetime import datetime from typing import Any from backend.data.contracts import DataUsage from backend.data.datahub import DatahubAwareTushareClient, DatahubBridge, DatahubClient, DatahubSettings from backend.data.datahub.ifind_proxy import HubIfindProxy from backend.data.datahub.realtime_proxy import HubRealtimeProxy from backend.data.policy import DataSourcePolicy from backend.data.providers import IfindProvider from backend.data.quality import DataQualityGate, QualityEvidence, QualityReport from backend.features.market.charts import MarketChartClient @dataclass(frozen=True) class DataGateway: policy: DataSourcePolicy quality: DataQualityGate ifind_provider: IfindProvider chart_data: MarketChartClient realtime_observer: HubRealtimeProxy datahub: DatahubBridge @property def ifind(self) -> HubIfindProxy: return self.ifind_provider.client def tushare( self, dataset_id: str = "", usage: DataUsage = "calculation", ) -> DatahubAwareTushareClient: if dataset_id: self.policy.assert_allowed(dataset_id, "tushare", usage) return DatahubAwareTushareClient(self.datahub) def dataset_status(self, trade_date: str) -> list[dict[str, Any]] | None: return self.datahub.dataset_status(trade_date) def batches(self, trade_date: str, dataset: str = "") -> list[dict[str, Any]] | None: return self.datahub.batches(trade_date, dataset) def datahub_status(self) -> dict[str, Any]: from backend.data.datahub.route_state import DATASET_LABELS, LEDGER from backend.data.datahub.settings import DATASETS settings = self.datahub.settings flags = [] enabled = 0 for name in DATASETS: read = bool(settings.flags(name).read) if read: enabled += 1 flags.append({"dataset": name, "label": DATASET_LABELS.get(name, name), "read": read}) routes = LEDGER.snapshot() fallbacks = [item for item in routes if item.get("route") == "legacy"] return { "configured": bool(settings.token and settings.base_url), "base_url": settings.base_url, "enabled_reads": enabled, "total_reads": len(DATASETS), "flags": flags, "routes": routes, "fallback_count": len(fallbacks), "fallback_labels": [str(item.get("label") or item.get("dataset")) for item in fallbacks], } def assert_source(self, dataset_id: str, provider_id: str, usage: DataUsage) -> None: self.policy.assert_allowed(dataset_id, provider_id, usage) def provider_chain(self, dataset_id: str, usage: DataUsage) -> tuple[str, ...]: dataset = self.policy.dataset(dataset_id) allowed = [] for provider_id in dataset.providers: try: self.policy.assert_allowed(dataset_id, provider_id, usage) except Exception: continue allowed.append(provider_id) if not allowed: raise RuntimeError(f"No permitted provider for {dataset_id} ({usage})") return tuple(allowed) def require_quality( self, evidence: QualityEvidence, usage: DataUsage, as_of: str | datetime | None = None, ) -> QualityReport: return self.quality.require(evidence, usage, as_of) def build_data_gateway( credentials: dict[str, object], datahub_settings: DatahubSettings | None = None, ) -> DataGateway: policy = DataSourcePolicy.load() settings = datahub_settings or DatahubSettings.load(credentials=credentials) datahub_client = DatahubClient(settings) datahub = DatahubBridge(settings, datahub_client) ifind = HubIfindProxy(datahub) return DataGateway( policy=policy, quality=DataQualityGate.load(policy), ifind_provider=IfindProvider(ifind), chart_data=MarketChartClient(datahub), realtime_observer=HubRealtimeProxy(datahub), datahub=datahub, )