1+ import redis
2+ from typing import Any , Optional
3+ import json
4+ import pickle
5+ from functools import wraps
6+
7+ from app .core .config import settings
8+
9+
10+ class RedisClient :
11+ def __init__ (self ):
12+ self ._client : Optional [redis .Redis ] = None
13+
14+ @property
15+ def client (self ) -> redis .Redis :
16+ if self ._client is None :
17+ self ._client = redis .from_url (
18+ settings .REDIS_URL ,
19+ password = settings .REDIS_PASSWORD ,
20+ db = settings .REDIS_DB ,
21+ decode_responses = False , # Keep binary data for pickle
22+ socket_connect_timeout = 5 ,
23+ socket_timeout = 5 ,
24+ retry_on_timeout = True ,
25+ )
26+ return self ._client
27+
28+ def ping (self ) -> bool :
29+ """Check Redis connection"""
30+ try :
31+ return bool (self .client .ping ())
32+ except redis .ConnectionError :
33+ return False
34+
35+ def get (self , key : str , default : Any = None ) -> Any :
36+ """Get value from Redis"""
37+ try :
38+ value = self .client .get (key )
39+ if value is None :
40+ return default
41+ # Try to deserialize
42+ try :
43+ return pickle .loads (value )
44+ except (pickle .PickleError , TypeError ):
45+ # Fallback to JSON or string
46+ try :
47+ return json .loads (value .decode ('utf-8' ))
48+ except (json .JSONDecodeError , UnicodeDecodeError ):
49+ return value .decode ('utf-8' )
50+ except redis .ConnectionError :
51+ return default
52+
53+ def set (
54+ self ,
55+ key : str ,
56+ value : Any ,
57+ expire : Optional [int ] = None ,
58+ serialize : bool = True
59+ ) -> bool :
60+ """Set value in Redis"""
61+ try :
62+ if serialize :
63+ serialized = pickle .dumps (value )
64+ else :
65+ serialized = str (value ).encode ('utf-8' )
66+
67+ return bool (self .client .set (key , serialized , ex = expire ))
68+ except redis .ConnectionError :
69+ return False
70+
71+ def delete (self , key : str ) -> bool :
72+ """Delete key from Redis"""
73+ try :
74+ return bool (self .client .delete (key ))
75+ except redis .ConnectionError :
76+ return False
77+
78+ def exists (self , key : str ) -> bool :
79+ """Check if key exists"""
80+ try :
81+ return bool (self .client .exists (key ))
82+ except redis .ConnectionError :
83+ return False
84+
85+ def flushdb (self ) -> bool :
86+ """Flush current database"""
87+ try :
88+ return bool (self .client .flushdb ())
89+ except redis .ConnectionError :
90+ return False
91+
92+ def close (self ):
93+ """Close Redis connection"""
94+ if self ._client :
95+ self ._client .close ()
96+ self ._client = None
97+
98+
99+ # Global Redis client instance
100+ redis_client = RedisClient ()
101+
102+
103+ def cache_result (expire : int = 3600 , key_prefix : str = "" ):
104+ """Decorator to cache function results"""
105+ def decorator (func ):
106+ @wraps (func )
107+ def wrapper (* args , ** kwargs ):
108+ # Generate cache key
109+ cache_key = f"{ key_prefix } :{ func .__name__ } :{ hash (str (args ) + str (sorted (kwargs .items ())))} "
110+
111+ # Try to get from cache
112+ cached_result = redis_client .get (cache_key )
113+ if cached_result is not None :
114+ return cached_result
115+
116+ # Execute function and cache result
117+ result = func (* args , ** kwargs )
118+ redis_client .set (cache_key , result , expire = expire )
119+ return result
120+
121+ return wrapper
122+ return decorator
0 commit comments