File size: 2,204 Bytes
cd0c7a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import logging
import redis
import hashlib
import json
import functools
from typing import Callable, Any
from app.config import settings

logger = logging.getLogger(__name__)

_redis = None
_cache_stats = {"hits": 0, "misses": 0}


def init_redis():
    global _redis
    try:
        _redis = redis.from_url(settings.REDIS_URL, decode_responses=True)
        _redis.ping()
        logger.info("Redis connected")
    except Exception:
        _redis = None
        logger.warning("Redis unavailable — caching disabled")


def get_redis():
    return _redis


def cache_get(key: str) -> str | None:
    r = get_redis()
    if r:
        val = r.get(key)
        if val is not None:
            _cache_stats["hits"] += 1
            return val
        _cache_stats["misses"] += 1
    return None


def cache_set(key: str, value: str, ttl: int = 86400):
    r = get_redis()
    if r:
        r.setex(key, ttl, value)


def get_cache_stats() -> dict:
    return {**_cache_stats, "redis_connected": _redis is not None}


def reset_cache_stats():
    _cache_stats["hits"] = 0
    _cache_stats["misses"] = 0


def ttl_cache(ttl: int = 86400, prefix: str = "cache"):
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        async def wrapper(self, input: dict, *args, **kwargs):
            raw = json.dumps(input, sort_keys=True)
            key_hash = hashlib.sha256(raw.encode()).hexdigest()[:16]
            cache_key = f"{prefix}:{key_hash}"

            cached = cache_get(cache_key)
            if cached is not None:
                try:
                    result = json.loads(cached)
                    if isinstance(result, dict):
                        result["from_cache"] = True
                    return result
                except (json.JSONDecodeError, TypeError):
                    pass

            result = await func(self, input, *args, **kwargs)
            try:
                cache_set(cache_key, json.dumps(result), ttl=ttl)
            except (TypeError, ValueError):
                pass
            if isinstance(result, dict):
                result["from_cache"] = False
            return result

        return wrapper

    return decorator