import time import json import logging logger = logging.getLogger(__name__) class CacheService: """ Servicio de caché híbrido para sesiones y lecturas rápidas de Moodle. Soporta Redis si está disponible en la infraestructura y hace fallback transparente a caché en memoria con TTL para entornos de desarrollo y pruebas. """ _memory_cache = {} _redis_client = None _redis_checked = False @classmethod def _get_redis(cls): if not cls._redis_checked: cls._redis_checked = True try: import redis from app.models.setting import SystemSetting redis_url = SystemSetting.get_value('redis_url', 'redis://127.0.0.1:6379/0') client = redis.from_url(redis_url, socket_connect_timeout=2) client.ping() cls._redis_client = client logger.info("Conexión exitosa a Redis en %s", redis_url) except Exception as e: cls._redis_client = None logger.info("Redis no disponible (%s). Operando con memoria local/TTL.", str(e).split('\n')[0]) return cls._redis_client @classmethod def get(cls, key: str, default=None): r = cls._get_redis() if r: try: val = r.get(key) if val is not None: return json.loads(val.decode('utf-8')) except Exception as e: logger.debug("Error leyendo de Redis: %s", e) # Fallback memoria item = cls._memory_cache.get(key) if item: val, expire_at = item if expire_at is None or expire_at > time.time(): return val else: del cls._memory_cache[key] return default @classmethod def set(cls, key: str, value, ttl_seconds: int = 300): r = cls._get_redis() if r: try: serialized = json.dumps(value) r.setex(key, ttl_seconds, serialized) return True except Exception as e: logger.debug("Error escribiendo en Redis: %s", e) expire_at = (time.time() + ttl_seconds) if ttl_seconds else None cls._memory_cache[key] = (value, expire_at) return True @classmethod def delete(cls, key: str): r = cls._get_redis() if r: try: r.delete(key) except Exception: pass cls._memory_cache.pop(key, None) @classmethod def clear(cls): cls._memory_cache.clear() cls._redis_checked = False cls._redis_client = None cache_service = CacheService()