# -*- coding: utf-8 -*- """看板聚合结果的内存 TTL 缓存。 缓存键包含数据库路径和数据版本;版本存放在 ``kv``(``cache:v:``), 因此同一个进程内的调度任务和 Web 线程都会看到一致的失效,跨进程的 CLI 导入也可以通过提升版本让旧缓存失效。 """ from __future__ import annotations import threading import time from typing import Any, Callable, Hashable TTL_VIDEOS = 3600 TTL_FANS = 86400 class TTLCache: def __init__(self, *, clock: Callable[[], float] = time.time, max_items: int = 256) -> None: self._clock = clock self._max_items = max(16, int(max_items)) self._lock = threading.RLock() self._items: dict[Hashable, tuple[float, Any]] = {} def get(self, key: Hashable) -> Any | None: with self._lock: entry = self._items.get(key) if entry is None: return None expires_at, value = entry if expires_at <= self._clock(): self._items.pop(key, None) return None return value def set(self, key: Hashable, ttl: float, value: Any) -> None: with self._lock: now = self._clock() self._items[key] = (now + float(ttl), value) if len(self._items) > self._max_items: expired = [k for k, (exp, _) in self._items.items() if exp <= now] for item in expired: self._items.pop(item, None) if len(self._items) > self._max_items: oldest = sorted(self._items.items(), key=lambda kv: kv[1][0]) for item, _ in oldest[: len(self._items) - self._max_items]: self._items.pop(item, None) def clear(self) -> None: with self._lock: self._items.clear() def __len__(self) -> int: with self._lock: return len(self._items) DASHBOARD_CACHE = TTLCache() def build_cache_key( scope: str, group: str, version: int, params: dict[str, Any] | None = None, ) -> tuple[Any, ...]: frozen = tuple(sorted((str(key), str(value)) for key, value in (params or {}).items())) return (scope, group, int(version), frozen)