mirror of
https://github.com/sstent/AICycling_mcp.git
synced 2026-01-25 16:42:24 +00:00
147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cache Manager - Handles caching of MCP responses and other data
|
|
"""
|
|
|
|
import time
|
|
import logging
|
|
from typing import Any, Dict, Optional, List
|
|
from dataclasses import dataclass
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@dataclass
|
|
class CacheEntry:
|
|
"""Cache entry with TTL"""
|
|
data: Any
|
|
timestamp: float
|
|
ttl: int # Time to live in seconds
|
|
|
|
class CacheManager:
|
|
"""Manages caching of data with TTL support"""
|
|
|
|
def __init__(self, default_ttl: int = 300):
|
|
self.default_ttl = default_ttl
|
|
self._cache: Dict[str, CacheEntry] = {}
|
|
|
|
def set(self, key: str, data: Any, ttl: Optional[int] = None) -> None:
|
|
"""Set cache entry with TTL"""
|
|
ttl = ttl or self.default_ttl
|
|
self._cache[key] = CacheEntry(
|
|
data=data,
|
|
timestamp=time.time(),
|
|
ttl=ttl
|
|
)
|
|
logger.debug(f"Cached data for key '{key}' with TTL {ttl}s")
|
|
|
|
def get(self, key: str, default: Any = None) -> Any:
|
|
"""Get cache entry, return default if expired or missing"""
|
|
if key not in self._cache:
|
|
logger.debug(f"Cache miss for key '{key}'")
|
|
return default
|
|
|
|
entry = self._cache[key]
|
|
|
|
# Check if expired
|
|
if time.time() - entry.timestamp > entry.ttl:
|
|
logger.debug(f"Cache expired for key '{key}'")
|
|
del self._cache[key]
|
|
return default
|
|
|
|
logger.debug(f"Cache hit for key '{key}'")
|
|
return entry.data
|
|
|
|
def has(self, key: str) -> bool:
|
|
"""Check if key exists and is not expired"""
|
|
return self.get(key) is not None
|
|
|
|
def delete(self, key: str) -> bool:
|
|
"""Delete cache entry"""
|
|
if key in self._cache:
|
|
del self._cache[key]
|
|
logger.debug(f"Deleted cache entry for key '{key}'")
|
|
return True
|
|
return False
|
|
|
|
def clear(self) -> None:
|
|
"""Clear all cache entries"""
|
|
count = len(self._cache)
|
|
self._cache.clear()
|
|
logger.debug(f"Cleared {count} cache entries")
|
|
|
|
def cleanup_expired(self) -> int:
|
|
"""Remove expired cache entries and return count removed"""
|
|
current_time = time.time()
|
|
expired_keys = [
|
|
key for key, entry in self._cache.items()
|
|
if current_time - entry.timestamp > entry.ttl
|
|
]
|
|
|
|
for key in expired_keys:
|
|
del self._cache[key]
|
|
|
|
if expired_keys:
|
|
logger.debug(f"Cleaned up {len(expired_keys)} expired cache entries")
|
|
|
|
return len(expired_keys)
|
|
|
|
def get_all(self) -> Dict[str, Any]:
|
|
"""Get all non-expired cache entries"""
|
|
self.cleanup_expired()
|
|
return {key: entry.data for key, entry in self._cache.items()}
|
|
|
|
def get_stats(self) -> Dict[str, Any]:
|
|
"""Get cache statistics"""
|
|
self.cleanup_expired()
|
|
return {
|
|
"total_entries": len(self._cache),
|
|
"keys": list(self._cache.keys()),
|
|
"memory_usage_estimate": sum(
|
|
len(str(entry.data)) for entry in self._cache.values()
|
|
)
|
|
}
|
|
|
|
def set_multiple(self, data: Dict[str, Any], ttl: Optional[int] = None) -> None:
|
|
"""Set multiple cache entries at once"""
|
|
for key, value in data.items():
|
|
self.set(key, value, ttl)
|
|
|
|
def get_multiple(self, keys: List[str]) -> Dict[str, Any]:
|
|
"""Get multiple cache entries at once"""
|
|
return {key: self.get(key) for key in keys}
|
|
|
|
# Specialized cache for common cycling data patterns
|
|
class CyclingDataCache(CacheManager):
|
|
"""Specialized cache for cycling data with helper methods"""
|
|
|
|
def cache_user_profile(self, profile_data: Dict[str, Any]) -> None:
|
|
"""Cache user profile data"""
|
|
self.set("user_profile", profile_data, ttl=3600) # 1 hour TTL
|
|
|
|
def cache_activities(self, activities: List[Dict[str, Any]]) -> None:
|
|
"""Cache activities list"""
|
|
self.set("recent_activities", activities, ttl=900) # 15 minutes TTL
|
|
|
|
def cache_activity_details(self, activity_id: str, details: Dict[str, Any]) -> None:
|
|
"""Cache specific activity details"""
|
|
self.set(f"activity_details_{activity_id}", details, ttl=3600)
|
|
|
|
def get_user_profile(self) -> Optional[Dict[str, Any]]:
|
|
"""Get cached user profile"""
|
|
return self.get("user_profile")
|
|
|
|
def get_recent_activities(self) -> List[Dict[str, Any]]:
|
|
"""Get cached recent activities"""
|
|
return self.get("recent_activities", [])
|
|
|
|
def get_activity_details(self, activity_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get cached activity details"""
|
|
return self.get(f"activity_details_{activity_id}")
|
|
|
|
def cache_workout_analysis(self, workout_id: str, analysis: str) -> None:
|
|
"""Cache workout analysis results"""
|
|
self.set(f"analysis_{workout_id}", analysis, ttl=86400) # 24 hours TTL
|
|
|
|
def get_workout_analysis(self, workout_id: str) -> Optional[str]:
|
|
"""Get cached workout analysis"""
|
|
return self.get(f"analysis_{workout_id}") |