# retoor from __future__ import annotations import time from collections import OrderedDict class TTLCache: def __init__(self, ttl: int, max_size: int = 0): self.ttl = ttl self.max_size = max_size self._store: OrderedDict[str, tuple[object, float]] = OrderedDict() def get(self, key: str): entry = self._store.get(key) if entry is None: return None value, expiry = entry if time.time() >= expiry: self._store.pop(key, None) return None self._store.move_to_end(key) return value def set(self, key: str, value) -> None: self._store[key] = (value, time.time() + self.ttl) self._store.move_to_end(key) if self.max_size and len(self._store) > self.max_size: self._store.popitem(last=False) def pop(self, key: str) -> None: self._store.pop(key, None) def clear(self) -> None: self._store.clear() class CacheStateGate: def __init__(self, *, ttl: int = 60, max_staleness: float = 5.0): self.ttl = ttl self.max_staleness = max_staleness self._cache = TTLCache(ttl=ttl) self._versions: dict[str, int] = {} self._bumped_at: dict[str, float] = {} def get_version(self, name: str) -> int: return int(self._versions.get(name, 0)) def bump(self, name: str) -> int: next_version = self.get_version(name) + 1 self._versions[name] = next_version self._bumped_at[name] = time.time() self._cache.clear() return next_version def get(self, key: str, version_name: str, loader): bumped_at = self._bumped_at.get(version_name, 0.0) if bumped_at and (time.time() - bumped_at) < self.max_staleness: self._cache.pop(key) version_key = f"{version_name}:{self.get_version(version_name)}" cached = self._cache.get(version_key) if cached is not None: return cached value = loader() self._cache.set(version_key, value) return value