"""In-memory cache for Short Hunter datasource responses.""" from __future__ import annotations import os import threading import time from copy import deepcopy from typing import Any, Dict, Optional def _ttl_seconds() -> int: return int(os.getenv("PROVIDER_CACHE_TTL_SECONDS", "30")) def _stale_seconds() -> int: return int(os.getenv("PROVIDER_STALE_CACHE_THRESHOLD_SECONDS", "300")) class GatewayCache: def __init__(self) -> None: self._lock = threading.Lock() self._items: Dict[str, Dict[str, Any]] = {} def set(self, key: str, payload: Dict[str, Any], *, provider: str, capability: str, ttl: Optional[int] = None) -> None: now = time.time() ttl_seconds = int(ttl or _ttl_seconds()) with self._lock: self._items[key] = { "key": key, "provider": provider, "capability": capability, "storedAt": now, "expiresAt": now + ttl_seconds, "staleAfter": now + _stale_seconds(), "payload": deepcopy(payload), } def get(self, key: str, *, allow_stale: bool = True) -> Optional[Dict[str, Any]]: now = time.time() with self._lock: item = self._items.get(key) if not item: return None if not allow_stale and now > item["expiresAt"]: return None result = deepcopy(item) result["ageSeconds"] = max(0.0, now - item["storedAt"]) result["isStale"] = now > item["staleAfter"] result["isExpired"] = now > item["expiresAt"] return result def snapshot(self) -> Dict[str, Any]: now = time.time() with self._lock: items = [] for item in self._items.values(): cloned = dict(item) cloned["ageSeconds"] = max(0.0, now - item["storedAt"]) cloned["isStale"] = now > item["staleAfter"] cloned["isExpired"] = now > item["expiresAt"] items.append(cloned) return { "size": len(items), "ttlSeconds": _ttl_seconds(), "staleThresholdSeconds": _stale_seconds(), "items": items, } gateway_cache = GatewayCache()