File size: 3,986 Bytes
565a379
 
 
 
 
 
 
3a4bdd3
 
565a379
 
3a4bdd3
565a379
3a4bdd3
 
 
 
 
 
 
 
 
 
c43db9b
565a379
3a4bdd3
565a379
3a4bdd3
565a379
3a4bdd3
 
 
 
565a379
3a4bdd3
 
 
 
 
 
 
565a379
3a4bdd3
 
 
565a379
3a4bdd3
 
565a379
 
3a4bdd3
 
565a379
3a4bdd3
 
565a379
3a4bdd3
565a379
 
 
 
 
3a4bdd3
 
 
 
 
565a379
3a4bdd3
565a379
3a4bdd3
565a379
 
3a4bdd3
565a379
3a4bdd3
 
 
 
 
 
 
 
 
565a379
 
 
 
 
3a4bdd3
 
 
 
 
 
 
 
 
 
 
565a379
3a4bdd3
565a379
3a4bdd3
 
565a379
 
3a4bdd3
 
 
 
 
 
 
565a379
 
3a4bdd3
565a379
3a4bdd3
 
 
 
 
565a379
3a4bdd3
 
 
 
565a379
3a4bdd3
565a379
3a4bdd3
565a379
3a4bdd3
 
565a379
3a4bdd3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import json
import logging
from typing import Any, Dict, Optional

import redis
from redis.exceptions import RedisError

from app.core.settings import settings

logger = logging.getLogger(__name__)


class CacheManager:

    CACHE_PREFIX = "mathminds:cache:"
    MAX_CACHE_SIZE = 50000

    def __init__(
        self,
        redis_url: Optional[str] = None,
        connection_pool: Optional[redis.ConnectionPool] = None,
    ):

        self.redis_url = redis_url or settings.REDIS_URL
        self.redis_client = None

        try:

            if connection_pool:
                self.redis_client = redis.Redis(
                    connection_pool=connection_pool,
                    decode_responses=True,
                )
            else:
                self.redis_client = redis.from_url(
                    self.redis_url,
                    decode_responses=True,
                    socket_timeout=2,
                    socket_connect_timeout=2,
                )

            self.redis_client.ping()

            logger.info(f"Connected to Redis at {self.redis_url}")

        except RedisError as e:

            logger.error(f"Redis connection failed: {e}")
            self.redis_client = None

    def _serialize(self, data: Any) -> str:
        return json.dumps(data, default=str)

    def _prefixed(self, key: str) -> str:
        return f"{self.CACHE_PREFIX}{key}"

    def get_cached_answer(self, cache_key: str) -> Optional[Dict[str, Any]]:

        if not self.redis_client:
            return None

        try:

            key = self._prefixed(cache_key)

            data = self.redis_client.get(key)

            if data:
                logger.debug(f"Cache hit: {key}")
                return json.loads(data)

            return None

        except (RedisError, json.JSONDecodeError) as e:

            logger.error(f"Cache read error: {e}")
            return None

    def set_cached_answer(
        self,
        cache_key: str,
        answer: Dict[str, Any],
        ttl: int = 86400,
    ) -> bool:

        if not self.redis_client:
            return False

        try:

            key = self._prefixed(cache_key)

            serialized_data = self._serialize(answer)

            if len(serialized_data) > self.MAX_CACHE_SIZE:
                logger.warning("Cache skipped: payload too large")
                return False

            self.redis_client.setex(key, ttl, serialized_data)

            return True

        except (RedisError, TypeError) as e:

            logger.error(f"Cache write failed: {e}")
            return False

    def set_if_not_exists(
        self,
        cache_key: str,
        answer: Dict[str, Any],
        ttl: int = 86400,
    ) -> bool:

        if not self.redis_client:
            return False

        try:

            key = self._prefixed(cache_key)

            serialized_data = self._serialize(answer)

            result = self.redis_client.set(
                key,
                serialized_data,
                ex=ttl,
                nx=True,
            )

            return bool(result)

        except Exception as e:

            logger.error(f"set_if_not_exists failed: {e}")
            return False

    def delete(self, cache_key: str) -> bool:

        if not self.redis_client:
            return False

        try:

            key = self._prefixed(cache_key)

            return bool(self.redis_client.delete(key))

        except RedisError:

            return False

    def stats(self) -> Dict[str, Any]:

        if not self.redis_client:
            return {}

        try:

            info = self.redis_client.info()

            return {
                "used_memory": info.get("used_memory_human"),
                "connected_clients": info.get("connected_clients"),
                "keyspace_hits": info.get("keyspace_hits"),
                "keyspace_misses": info.get("keyspace_misses"),
            }

        except Exception:

            return {}