from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass from datetime import datetime from backend.data.contracts import DataUsage from backend.data.policy import DataSourcePolicy from backend.data.providers import IfindProvider, TushareProvider from backend.data.quality import DataQualityGate, QualityEvidence, QualityReport from chart_data_provider import EastmoneyChartClient, MarketChartClient from ifind_client import IfindHttpClient from realtime_aggregator import WebRealtimeAggregator from tushare_client import TushareClient @dataclass(frozen=True) class DataGateway: policy: DataSourcePolicy quality: DataQualityGate tushare_provider: TushareProvider ifind_provider: IfindProvider chart_data: MarketChartClient realtime_observer: WebRealtimeAggregator @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 self.tushare_provider.client() 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, ) -> 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() 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(), )