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