Files
dashboard-leads/backend/cache_manager.py

80 lines
2.1 KiB
Python

# backend/cache_manager.py
"""Caché global en memoria con refresco en segundo plano (igual que el dashboard de ventas)."""
import time
import threading
from typing import Any, Callable, Dict, Tuple
_CACHE: Dict[str, Tuple[float, Any]] = {}
_LOCK = threading.Lock()
_DEFAULT_TTL = 900 # 15 min
def _make_key(prefix: str, args: tuple) -> str:
return prefix + ":" + "|".join(str(a) for a in args)
def cache_get_or_set(prefix: str, args: tuple, loader: Callable[[], Any], ttl: int = _DEFAULT_TTL) -> Any:
key = _make_key(prefix, args)
now = time.time()
with _LOCK:
hit = _CACHE.get(key)
if hit and (now - hit[0] < ttl):
return hit[1]
value = loader()
with _LOCK:
_CACHE[key] = (now, value)
return value
def cache_keys():
with _LOCK:
return list(_CACHE.keys())
def cache_refresh_existing(loader_for_key):
for key in cache_keys():
try:
nuevo = loader_for_key(key)
if nuevo is not None:
with _LOCK:
_CACHE[key] = (time.time(), nuevo)
except Exception as e:
print(f"[cache refresh] {key}: {e}")
def cache_invalidate(prefix: str = None) -> None:
with _LOCK:
if prefix is None:
_CACHE.clear()
else:
for k in list(_CACHE.keys()):
if k.startswith(prefix + ":"):
del _CACHE[k]
def cache_stats() -> dict:
with _LOCK:
return {"entradas": len(_CACHE), "claves": list(_CACHE.keys())}
_background_started = False
def start_background_refresh(refresh_fn: Callable[[], None], interval: int = 900):
global _background_started
if _background_started:
return
_background_started = True
def _loop():
while True:
time.sleep(interval)
try:
refresh_fn()
except Exception as e:
print(f"[background refresh] error: {e}")
t = threading.Thread(target=_loop, daemon=True)
t.start()
print(f"[cache] refresco en segundo plano cada {interval}s iniciado")