הראה קוד מקור ל cache_manager

"""
מנהל Cache מתקדם עם Redis
Advanced Cache Manager with Redis
"""

import json
import logging
import os
from functools import wraps
from typing import Any, Dict, List, Optional, Union
import redis
import asyncio
from datetime import datetime, timedelta

logger = logging.getLogger(__name__)

[תיעוד] class CacheManager: """מנהל Cache מתקדם עם Redis"""
[תיעוד] def __init__(self): self.redis_client = None self.is_enabled = False self.connect()
[תיעוד] def connect(self): """התחברות ל-Redis""" try: redis_url = os.getenv('REDIS_URL') if not redis_url or redis_url.strip() == "" or redis_url.startswith("disabled"): self.is_enabled = False logger.info("Redis אינו מוגדר - Cache מושבת") return self.redis_client = redis.from_url( redis_url, decode_responses=True, socket_connect_timeout=5, socket_timeout=5, retry_on_timeout=True, health_check_interval=30 ) # בדיקת חיבור self.redis_client.ping() self.is_enabled = True logger.info("התחברות ל-Redis הצליחה - Cache מופעל") except Exception as e: logger.warning(f"לא ניתן להתחבר ל-Redis: {e} - Cache מושבת") self.is_enabled = False
def _make_key(self, prefix: str, *args, **kwargs) -> str: """יוצר מפתח cache ייחודי""" key_parts = [prefix] key_parts.extend(str(arg) for arg in args) if kwargs: sorted_kwargs = sorted(kwargs.items()) key_parts.extend(f"{k}:{v}" for k, v in sorted_kwargs) return ":".join(key_parts)
[תיעוד] def get(self, key: str) -> Optional[Any]: """קבלת ערך מה-cache""" if not self.is_enabled: return None try: value = self.redis_client.get(key) if value: return json.loads(value) except Exception as e: logger.error(f"שגיאה בקריאה מ-cache: {e}") return None
[תיעוד] def set(self, key: str, value: Any, expire_seconds: int = 300) -> bool: """שמירת ערך ב-cache""" if not self.is_enabled: return False try: serialized = json.dumps(value, default=str, ensure_ascii=False) return self.redis_client.setex(key, expire_seconds, serialized) except Exception as e: logger.error(f"שגיאה בכתיבה ל-cache: {e}") return False
[תיעוד] def delete(self, key: str) -> bool: """מחיקת ערך מה-cache""" if not self.is_enabled: return False try: return bool(self.redis_client.delete(key)) except Exception as e: logger.error(f"שגיאה במחיקה מ-cache: {e}") return False
[תיעוד] def delete_pattern(self, pattern: str) -> int: """מחיקת כל המפתחות שמתאימים לתבנית""" if not self.is_enabled: return 0 try: keys = self.redis_client.keys(pattern) if keys: return self.redis_client.delete(*keys) return 0 except Exception as e: logger.error(f"שגיאה במחיקת תבנית מ-cache: {e}") return 0
[תיעוד] def invalidate_user_cache(self, user_id: int): """מחיקת כל ה-cache של משתמש ספציפי""" # התאמה רחבה יותר למפתחות כפי שהם נוצרים כיום ב-_make_key # המפתחות נראים כך: "<prefix>:<func_name>:<self>:<user_id>:..." # לכן נמחק לפי prefixes הרלוונטיים ולפי user_id גולמי. total_deleted = 0 try: patterns = [ f"*:user:{user_id}:*", # תמיכה לאחור אם יתווסף prefix "user:" בעתיד f"user_files:*:{user_id}:*", # רשימת קבצי משתמש f"latest_version:*:{user_id}:*", # גרסה אחרונה לקובץ f"search_code:*:{user_id}:*", # תוצאות חיפוש למשתמש f"*:{user_id}:*", # נפילה לאחור: כל מפתח שמכיל את המזהה ] for p in patterns: total_deleted += int(self.delete_pattern(p) or 0) except Exception as e: logger.warning(f"invalidate_user_cache failed for user {user_id}: {e}") logger.info(f"נמחקו {total_deleted} ערכי cache עבור משתמש {user_id}") return total_deleted
[תיעוד] def get_stats(self) -> Dict[str, Any]: """סטטיסטיקות cache""" if not self.is_enabled: return {"enabled": False} try: info = self.redis_client.info() return { "enabled": True, "used_memory": info.get('used_memory_human', 'N/A'), "connected_clients": info.get('connected_clients', 0), "keyspace_hits": info.get('keyspace_hits', 0), "keyspace_misses": info.get('keyspace_misses', 0), "hit_rate": round( info.get('keyspace_hits', 0) / max(info.get('keyspace_hits', 0) + info.get('keyspace_misses', 0), 1) * 100, 2 ) } except Exception as e: logger.error(f"שגיאה בקבלת סטטיסטיקות cache: {e}") return {"enabled": True, "error": str(e)}
# יצירת instance גלובלי cache = CacheManager()
[תיעוד] def cached(expire_seconds: int = 300, key_prefix: str = "default"): """דקורטור לcaching פונקציות""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # יצירת מפתח cache cache_key = cache._make_key(key_prefix, func.__name__, *args, **kwargs) # בדיקה ב-cache result = cache.get(cache_key) if result is not None: logger.debug(f"Cache hit: {cache_key}") return result # הפעלת הפונקציה ושמירה ב-cache result = func(*args, **kwargs) cache.set(cache_key, result, expire_seconds) logger.debug(f"Cache miss, stored: {cache_key}") return result return wrapper return decorator
[תיעוד] def async_cached(expire_seconds: int = 300, key_prefix: str = "default"): """דקורטור לcaching פונקציות async""" def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): # יצירת מפתח cache cache_key = cache._make_key(key_prefix, func.__name__, *args, **kwargs) # בדיקה ב-cache result = cache.get(cache_key) if result is not None: logger.debug(f"Cache hit: {cache_key}") return result # הפעלת הפונקציה ושמירה ב-cache result = await func(*args, **kwargs) cache.set(cache_key, result, expire_seconds) logger.debug(f"Cache miss, stored: {cache_key}") return result return wrapper return decorator