1- """Redis caching utilities for API responses."""
1+ """In-memory caching utilities for API responses."""
22
33import hashlib
44import json
5+ import time
56from functools import lru_cache
67from typing import Any
78
8- import redis
99import structlog
1010
1111from src .config import get_settings
1212
1313logger = structlog .get_logger ()
1414
1515
16- class RedisCache :
17- """Redis-based caching for API responses."""
16+ class MemoryCache :
17+ """Simple in-memory caching for API responses."""
1818
19- def __init__ (self , url : str , default_ttl : int = 3600 ) -> None :
20- """Initialize Redis cache.
19+ def __init__ (self , default_ttl : int = 3600 ) -> None :
20+ """Initialize memory cache.
2121
2222 Args:
23- url: Redis connection URL
2423 default_ttl: Default TTL in seconds
2524 """
26- self ._client : redis .Redis | None = None
27- self ._url = url
25+ self ._cache : dict [str , tuple [Any , float ]] = {}
2826 self ._default_ttl = default_ttl
29- self ._connected = False
30-
31- def _connect (self ) -> redis .Redis | None :
32- """Establish Redis connection with error handling."""
33- if self ._client is not None :
34- return self ._client
35-
36- try :
37- self ._client = redis .from_url (
38- self ._url ,
39- decode_responses = True ,
40- socket_connect_timeout = 5 ,
41- )
42- # Test connection
43- self ._client .ping ()
44- self ._connected = True
45- logger .info ("redis_connected" , url = self ._url )
46- return self ._client
47- except redis .ConnectionError as e :
48- logger .warning ("redis_connection_failed" , error = str (e ))
49- self ._connected = False
50- return None
5127
5228 @staticmethod
5329 def _make_key (prefix : str , * args : Any , ** kwargs : Any ) -> str :
@@ -56,53 +32,51 @@ def _make_key(prefix: str, *args: Any, **kwargs: Any) -> str:
5632 key_hash = hashlib .sha256 (key_data .encode ()).hexdigest ()[:16 ]
5733 return f"{ prefix } :{ key_hash } "
5834
35+ def _cleanup_expired (self ) -> None :
36+ """Remove expired entries from cache."""
37+ now = time .time ()
38+ expired = [k for k , (_ , exp ) in self ._cache .items () if exp < now ]
39+ for key in expired :
40+ del self ._cache [key ]
41+
5942 def get (self , key : str ) -> Any | None :
6043 """Get value from cache.
6144
6245 Args:
6346 key: Cache key
6447
6548 Returns:
66- Cached value or None if not found
49+ Cached value or None if not found or expired
6750 """
68- client = self ._connect ()
69- if client is None :
70- return None
51+ self ._cleanup_expired ()
7152
72- try :
73- value = client . get ( key )
74- if value :
53+ if key in self . _cache :
54+ value , expiry = self . _cache [ key ]
55+ if expiry > time . time () :
7556 logger .debug ("cache_hit" , key = key )
76- return json . loads ( value )
77- logger . debug ( "cache_miss" , key = key )
78- return None
79- except ( redis . RedisError , json . JSONDecodeError ) as e :
80- logger .warning ( "cache_get_error " , key = key , error = str ( e ) )
81- return None
57+ return value
58+ else :
59+ del self . _cache [ key ]
60+
61+ logger .debug ( "cache_miss " , key = key )
62+ return None
8263
8364 def set (self , key : str , value : Any , ttl : int | None = None ) -> bool :
8465 """Set value in cache.
8566
8667 Args:
8768 key: Cache key
88- value: Value to cache (must be JSON serializable)
69+ value: Value to cache
8970 ttl: Time to live in seconds (uses default if not specified)
9071
9172 Returns:
92- True if successful, False otherwise
73+ True if successful
9374 """
94- client = self ._connect ()
95- if client is None :
96- return False
97-
98- try :
99- ttl = ttl or self ._default_ttl
100- client .setex (key , ttl , json .dumps (value ))
101- logger .debug ("cache_set" , key = key , ttl = ttl )
102- return True
103- except (redis .RedisError , TypeError ) as e :
104- logger .warning ("cache_set_error" , key = key , error = str (e ))
105- return False
75+ ttl = ttl or self ._default_ttl
76+ expiry = time .time () + ttl
77+ self ._cache [key ] = (value , expiry )
78+ logger .debug ("cache_set" , key = key , ttl = ttl )
79+ return True
10680
10781 def delete (self , key : str ) -> bool :
10882 """Delete value from cache.
@@ -111,31 +85,26 @@ def delete(self, key: str) -> bool:
11185 key: Cache key
11286
11387 Returns:
114- True if deleted, False otherwise
88+ True if deleted, False if not found
11589 """
116- client = self ._connect ()
117- if client is None :
118- return False
119-
120- try :
121- client .delete (key )
90+ if key in self ._cache :
91+ del self ._cache [key ]
12292 logger .debug ("cache_delete" , key = key )
12393 return True
124- except redis .RedisError as e :
125- logger .warning ("cache_delete_error" , key = key , error = str (e ))
126- return False
94+ return False
12795
12896 @property
12997 def is_connected (self ) -> bool :
130- """Check if Redis is connected."""
131- return self ._connected
98+ """Always connected for memory cache."""
99+ return True
100+
101+
102+ # Backwards compatibility aliases
103+ RedisCache = MemoryCache
132104
133105
134106@lru_cache
135- def get_cache () -> RedisCache :
136- """Get cached Redis cache instance."""
107+ def get_cache () -> MemoryCache :
108+ """Get cached memory cache instance."""
137109 settings = get_settings ()
138- return RedisCache (
139- url = settings .redis_url ,
140- default_ttl = settings .cache_ttl_seconds ,
141- )
110+ return MemoryCache (default_ttl = settings .cache_ttl_seconds )
0 commit comments