新增独立 xiaobai-datahub 服务(SQLite WAL、Tushare 盘后发布、/v1 契约和管理后台),不改现站页面与数据链路。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
85 lines
3.0 KiB
Python
85 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from datahub.db import HubDB
|
|
from datahub.timeutil import isoformat, now_shanghai
|
|
|
|
|
|
class LastKnownGood:
|
|
def __init__(self, db: HubDB) -> None:
|
|
self.db = db
|
|
|
|
def store(self, cache_key: str, payload: Any, source: str) -> None:
|
|
self.db.execute(
|
|
"""
|
|
INSERT INTO last_known_good(cache_key, payload, source, stored_at)
|
|
VALUES (?, ?, ?, ?)
|
|
ON CONFLICT(cache_key) DO UPDATE SET
|
|
payload=excluded.payload, source=excluded.source, stored_at=excluded.stored_at
|
|
""",
|
|
(cache_key, json.dumps(payload, ensure_ascii=False), source, isoformat()),
|
|
)
|
|
|
|
def load(self, cache_key: str) -> dict[str, Any] | None:
|
|
row = self.db.fetchone("SELECT * FROM last_known_good WHERE cache_key = ?", (cache_key,))
|
|
if not row:
|
|
return None
|
|
return {
|
|
"payload": json.loads(row["payload"]),
|
|
"source": row["source"],
|
|
"stored_at": row["stored_at"],
|
|
}
|
|
|
|
def put_rt(self, cache_key: str, payload: Any, source: str, ttl_seconds: int) -> None:
|
|
now = now_shanghai()
|
|
expires = isoformat(now.replace(microsecond=0))
|
|
# expires_at stored as iso; compute by adding ttl via timestamp
|
|
from datetime import timedelta
|
|
|
|
self.db.execute(
|
|
"""
|
|
INSERT INTO rt_cache(cache_key, payload, source, stored_at, expires_at)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
ON CONFLICT(cache_key) DO UPDATE SET
|
|
payload=excluded.payload, source=excluded.source,
|
|
stored_at=excluded.stored_at, expires_at=excluded.expires_at
|
|
""",
|
|
(
|
|
cache_key,
|
|
json.dumps(payload, ensure_ascii=False),
|
|
source,
|
|
isoformat(now),
|
|
isoformat(now + timedelta(seconds=ttl_seconds)),
|
|
),
|
|
)
|
|
self.store(cache_key, payload, source)
|
|
|
|
def get_rt(self, cache_key: str, max_stale_seconds: int | None = None) -> dict[str, Any] | None:
|
|
row = self.db.fetchone("SELECT * FROM rt_cache WHERE cache_key = ?", (cache_key,))
|
|
if not row:
|
|
lkg = self.load(cache_key)
|
|
if not lkg:
|
|
return None
|
|
return {**lkg, "stale": True}
|
|
stored_at = row["stored_at"]
|
|
expired = row["expires_at"] < isoformat()
|
|
result = {
|
|
"payload": json.loads(row["payload"]),
|
|
"source": row["source"],
|
|
"stored_at": stored_at,
|
|
"stale": expired,
|
|
}
|
|
if expired and max_stale_seconds is not None:
|
|
from datetime import datetime
|
|
|
|
try:
|
|
stored = datetime.fromisoformat(stored_at)
|
|
age = (now_shanghai() - stored).total_seconds()
|
|
except ValueError:
|
|
age = max_stale_seconds + 1
|
|
if age > max_stale_seconds:
|
|
return None
|
|
return result
|