-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.py
More file actions
101 lines (84 loc) · 3.53 KB
/
cache.py
File metadata and controls
101 lines (84 loc) · 3.53 KB
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
import redis
from typing import Optional, Any, Union
import json
import logging
from redis.exceptions import RedisError
from redis.backoff import ExponentialBackoff
from redis.retry import Retry
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class CacheError(Exception):
"""Base exception for cache operations"""
pass
class CacheConnectionError(CacheError):
"""Raised when Redis connection fails"""
pass
class CacheSerializationError(CacheError):
"""Raised when JSON serialization/deserialization fails"""
pass
def create_redis_client() -> redis.Redis:
"""Create Redis client with retry mechanism"""
retry = Retry(ExponentialBackoff(), 3) # Retry 3 times with exponential backoff
return redis.Redis(
host='localhost',
port=6379,
db=0,
decode_responses=True,
socket_timeout=5,
socket_connect_timeout=5,
socket_keepalive=True,
retry_on_timeout=True,
retry=retry,
health_check_interval=30
)
redis_client = create_redis_client()
def validate_cache_key(key: str) -> None:
"""Validate cache key for length and safe characters"""
if not isinstance(key, str):
raise ValueError("Cache key must be a string")
if not key:
raise ValueError("Cache key cannot be empty")
if len(key) > 512: # Redis default max key length
raise ValueError("Cache key exceeds maximum length")
safe_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./:')
if not all(c in safe_chars for c in key):
raise ValueError("Cache key contains invalid characters")
def get_cache(key: str, timeout: int = 2) -> Optional[Any]:
"""Get value from Redis cache with improved error handling and timeout"""
validate_cache_key(key)
try:
value = redis_client.get(key, timeout=timeout)
if value is None:
return None
try:
return json.loads(value)
except json.JSONDecodeError as e:
logger.error(f"Failed to deserialize cache value for key {key}: {e}")
raise CacheSerializationError(f"Failed to deserialize cache value: {e}")
except redis.ConnectionError as e:
logger.error(f"Redis connection error: {e}")
raise CacheConnectionError(f"Failed to connect to Redis: {e}")
except RedisError as e:
logger.error(f"Redis error when getting key {key}: {e}")
raise CacheError(f"Cache operation failed: {e}")
def set_cache(key: str, value: Any, expire: int = 3600, timeout: int = 2) -> bool:
"""Set value in Redis cache with expiration, validation and timeout"""
validate_cache_key(key)
if expire <= 0:
raise ValueError("Expiration time must be positive")
if expire > 2592000: # 30 days in seconds
raise ValueError("Expiration time cannot exceed 30 days")
try:
serialized = json.dumps(value)
except (TypeError, ValueError) as e:
logger.error(f"Failed to serialize value for key {key}: {e}")
raise CacheSerializationError(f"Failed to serialize value: {e}")
try:
return bool(redis_client.setex(key, expire, serialized, timeout=timeout))
except redis.ConnectionError as e:
logger.error(f"Redis connection error: {e}")
raise CacheConnectionError(f"Failed to connect to Redis: {e}")
except RedisError as e:
logger.error(f"Redis error when setting key {key}: {e}")
raise CacheError(f"Cache operation failed: {e}")