from __future__ import annotations from collections.abc import Callable 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.policy import DataSourcePolicy from backend.data.providers import IfindProvider, TushareProvider from backend.data.quality import DataQualityGate, QualityEvidence, QualityReport from backend.data.providers.ifind_client import IfindHttpClient from backend.data.providers.tushare_client import TushareClient from backend.data.realtime import WebRealtimeAggregator from backend.features.market.charts import EastmoneyChartClient, MarketChartClient @dataclass(frozen=True) class DataGateway: policy: DataSourcePolicy quality: DataQualityGate tushare_provider: TushareProvider ifind_provider: IfindProvider chart_data: MarketChartClient realtime_observer: WebRealtimeAggregator datahub: DatahubBridge @property def ifind(self) -> IfindHttpClient: return self.ifind_provider.client def tushare( self, dataset_id: str = "", usage: DataUsage = "calculation", ) -> TushareClient: if dataset_id: self.policy.assert_allowed(dataset_id, "tushare", usage) return DatahubAwareTushareClient(self.tushare_provider.client(), 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 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], tushare_token_supplier: Callable[[], str] | None = None, datahub_settings: DatahubSettings | None = None, ) -> DataGateway: ifind = IfindHttpClient( str(credentials.get("ifind_refresh_token") or ""), str(credentials.get("ifind_access_token") or ""), ) token_supplier = tushare_token_supplier or ( lambda: str(credentials.get("tushare_token") or "") ) policy = DataSourcePolicy.load() settings = datahub_settings or DatahubSettings.load(credentials=credentials) datahub_client = DatahubClient(settings) return DataGateway( policy=policy, quality=DataQualityGate.load(policy), tushare_provider=TushareProvider(token_supplier), ifind_provider=IfindProvider(ifind), chart_data=MarketChartClient(ifind, EastmoneyChartClient()), realtime_observer=WebRealtimeAggregator(), datahub=DatahubBridge(settings, datahub_client), )