Ctrl K
ComunidadGuías…

backend/cache_response.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Response caching for read-heavy GET endpoints.

Uses app.cache_backend to cache full JSON response body + status code
with a short TTL. Invalidate on config/profile changes via cache key prefix.
"""

import json
import logging
from functools import wraps

from flask import Response, current_app, request

logger = logging.getLogger(__name__)

DEFAULT_TTL = 60  # seconds


def cached_get(ttl_seconds: int = DEFAULT_TTL, key_prefix: str = "response:get:"):
    """Decorator: cache GET response in app.cache_backend.

    Only applies to GET. Cache key is key_prefix + request.path + '?' + query_string.
    Cached value is JSON: {"status": int, "body": str}.
    """

    def decorator(f):
        @wraps(f)
        def inner(*args, **kwargs):
            if request.method != "GET":
                return f(*args, **kwargs)

            cache = getattr(current_app, "cache_backend", None)
            if cache is None:
                return f(*args, **kwargs)

            key = key_prefix + request.path
            if request.query_string:
                key += "?" + request.query_string.decode("utf-8", errors="replace")

            try:
                raw = cache.get(key)
            except Exception as e:
                logger.debug("Response cache get failed: %s", e)
                raw = None

            if raw is not None:
                try:
                    data = json.loads(raw)
                    return Response(
                        data["body"],
                        status=data.get("status", 200),
                        mimetype="application/json",
                    )
                except (json.JSONDecodeError, KeyError, TypeError):
                    pass

            result = f(*args, **kwargs)
            if isinstance(result, tuple):
                resp, status = result[0], result[1]
                headers = result[2] if len(result) > 2 else None
            else:
                resp = result
                status = 200
                headers = None

            try:
                body = resp.get_data(as_text=True) if hasattr(resp, "get_data") else None
                if body is not None:
                    cache.set(
                        key,
                        json.dumps({"status": status, "body": body}),
                        ttl_seconds=ttl_seconds,
                    )
            except Exception as e:
                logger.debug("Response cache set failed: %s", e)

            if headers:
                return resp, status, headers
            return resp, status

        return inner

    return decorator


def invalidate_response_cache():
    """Clear all cached GET responses (call after config or profile changes)."""
    cache = getattr(current_app, "cache_backend", None)
    if cache is None:
        return
    try:
        n = cache.clear(prefix="response:get:")
        if n:
            logger.debug("Invalidated %d response cache entries", n)
    except Exception as e:
        logger.debug("Response cache invalidation failed: %s", e)