默认全部读取/影子开关关闭,网站继续走旧 Tushare 链路;开启单项时只替换该类原料并在失败时回旧,问天保持旧路径。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
121 lines
3.5 KiB
Python
121 lines
3.5 KiB
Python
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,
|
|
)
|