Files
aitbc/aitbc/redis_cache.py
aitbc 40cee6d791
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
API Endpoint Tests / test-api-endpoints (push) Successful in 20s
CLI Tests / test-cli (push) Failing after 3s
Package Tests / Python package - aitbc-agent-sdk (push) Successful in 33s
Package Tests / Python package - aitbc-core (push) Failing after 1s
Package Tests / Python package - aitbc-crypto (push) Successful in 10s
Package Tests / Python package - aitbc-sdk (push) Successful in 9s
Package Tests / JavaScript package - aitbc-sdk-js (push) Successful in 10s
Package Tests / JavaScript package - aitbc-token (push) Successful in 17s
Production Tests / Production Integration Tests (push) Failing after 6s
refactor: enhance configuration with security validation, database pooling, and rate limiting
- Added List import and field_validator to config.py
- Added database connection pooling settings (max_overflow, pool_recycle, pool_pre_ping, echo)
- Added rate limiting settings (rate_limit_requests, rate_limit_window_seconds)
- Added CORS allow_origins field with default empty list
- Added validate_secrets() method to check required secrets in production
- Added validate_secret_length() validator for secret_key and jwt_secret (minimum
2026-05-12 21:17:54 +02:00

329 lines
8.8 KiB
Python

"""
Redis caching utilities for AITBC applications
Provides distributed caching with Redis backend
"""
from typing import Optional, Any, List
import json
import hashlib
from .aitbc_logging import get_logger
logger = get_logger(__name__)
try:
import redis
REDIS_AVAILABLE = True
except ImportError:
REDIS_AVAILABLE = False
logger.warning("Redis package not installed. Caching will be disabled.")
class RedisCache:
"""
Redis cache implementation for distributed caching
"""
def __init__(
self,
redis_url: Optional[str] = None,
max_connections: int = 10,
timeout: int = 5,
default_ttl: int = 3600
):
"""
Initialize Redis cache
Args:
redis_url: Redis connection URL (e.g., redis://localhost:6379/0)
max_connections: Maximum number of connections
timeout: Connection timeout in seconds
default_ttl: Default time-to-live for cached items in seconds
"""
self.redis_url = redis_url
self.max_connections = max_connections
self.timeout = timeout
self.default_ttl = default_ttl
self._client = None
if REDIS_AVAILABLE and redis_url:
try:
self._client = redis.Redis.from_url(
redis_url,
max_connections=max_connections,
socket_timeout=timeout,
socket_connect_timeout=timeout,
decode_responses=True
)
# Test connection
self._client.ping()
logger.info(f"Connected to Redis at {redis_url}")
except Exception as e:
logger.warning(f"Failed to connect to Redis: {e}")
self._client = None
else:
logger.info("Redis caching disabled (Redis not available or no URL provided)")
def is_available(self) -> bool:
"""Check if Redis cache is available"""
return self._client is not None
def get(self, key: str) -> Optional[Any]:
"""
Get value from cache
Args:
key: Cache key
Returns:
Cached value or None if not found
"""
if not self.is_available():
return None
try:
value = self._client.get(key)
if value:
return json.loads(value)
return None
except Exception as e:
logger.error(f"Redis get error: {e}")
return None
def set(
self,
key: str,
value: Any,
ttl: Optional[int] = None
) -> bool:
"""
Set value in cache
Args:
key: Cache key
value: Value to cache (must be JSON serializable)
ttl: Time-to-live in seconds (uses default_ttl if not provided)
Returns:
True if successful, False otherwise
"""
if not self.is_available():
return False
try:
serialized = json.dumps(value)
expiry = ttl if ttl is not None else self.default_ttl
self._client.setex(key, expiry, serialized)
return True
except Exception as e:
logger.error(f"Redis set error: {e}")
return False
def delete(self, key: str) -> bool:
"""
Delete value from cache
Args:
key: Cache key
Returns:
True if successful, False otherwise
"""
if not self.is_available():
return False
try:
self._client.delete(key)
return True
except Exception as e:
logger.error(f"Redis delete error: {e}")
return False
def exists(self, key: str) -> bool:
"""
Check if key exists in cache
Args:
key: Cache key
Returns:
True if key exists, False otherwise
"""
if not self.is_available():
return False
try:
return self._client.exists(key) == 1
except Exception as e:
logger.error(f"Redis exists error: {e}")
return False
def clear(self) -> bool:
"""
Clear all cached values
Returns:
True if successful, False otherwise
"""
if not self.is_available():
return False
try:
self._client.flushdb()
return True
except Exception as e:
logger.error(f"Redis clear error: {e}")
return False
def get_many(self, keys: List[str]) -> dict[str, Any]:
"""
Get multiple values from cache
Args:
keys: List of cache keys
Returns:
Dictionary mapping keys to cached values
"""
if not self.is_available():
return {}
try:
values = self._client.mget(keys)
result = {}
for key, value in zip(keys, values):
if value:
result[key] = json.loads(value)
return result
except Exception as e:
logger.error(f"Redis get_many error: {e}")
return {}
def set_many(self, mapping: dict[str, Any], ttl: Optional[int] = None) -> bool:
"""
Set multiple values in cache
Args:
mapping: Dictionary of key-value pairs
ttl: Time-to-live in seconds
Returns:
True if successful, False otherwise
"""
if not self.is_available():
return False
try:
pipe = self._client.pipeline()
expiry = ttl if ttl is not None else self.default_ttl
for key, value in mapping.items():
serialized = json.dumps(value)
pipe.setex(key, expiry, serialized)
pipe.execute()
return True
except Exception as e:
logger.error(f"Redis set_many error: {e}")
return False
def delete_many(self, keys: List[str]) -> bool:
"""
Delete multiple values from cache
Args:
keys: List of cache keys
Returns:
True if successful, False otherwise
"""
if not self.is_available():
return False
try:
if keys:
self._client.delete(*keys)
return True
except Exception as e:
logger.error(f"Redis delete_many error: {e}")
return False
def increment(self, key: str, amount: int = 1) -> Optional[int]:
"""
Increment a counter in cache
Args:
key: Cache key
amount: Amount to increment by
Returns:
New value or None if failed
"""
if not self.is_available():
return None
try:
return self._client.incrby(key, amount)
except Exception as e:
logger.error(f"Redis increment error: {e}")
return None
def close(self) -> None:
"""Close Redis connection"""
if self._client:
self._client.close()
logger.info("Redis connection closed")
# Global cache instance
_global_cache: Optional[RedisCache] = None
def get_cache(
redis_url: Optional[str] = None,
max_connections: int = 10,
timeout: int = 5,
default_ttl: int = 3600
) -> RedisCache:
"""
Get or create global Redis cache instance
Args:
redis_url: Redis connection URL
max_connections: Maximum number of connections
timeout: Connection timeout in seconds
default_ttl: Default time-to-live for cached items
Returns:
RedisCache instance
"""
global _global_cache
if _global_cache is None and redis_url:
_global_cache = RedisCache(
redis_url=redis_url,
max_connections=max_connections,
timeout=timeout,
default_ttl=default_ttl
)
return _global_cache or RedisCache() # Return disabled cache if no URL
def cache_key(*parts: str, prefix: str = "aitbc") -> str:
"""
Generate a cache key from parts
Args:
*parts: Parts to include in the key
prefix: Key prefix
Returns:
Cache key string
"""
key_string = ":".join(str(part) for part in parts)
full_key = f"{prefix}:{key_string}"
# Hash if too long
if len(full_key) > 250:
hash_value = hashlib.sha256(full_key.encode()).hexdigest()[:16]
return f"{prefix}:hashed:{hash_value}"
return full_key