Skip to main content
Glama
vzeman
by vzeman
cache_service.py5.65 kB
"""Cache service for Odoo MCP Server.""" import time from typing import Any, Dict, Optional, Union from threading import Lock from ..config import get_config from ..logger import get_logger logger = get_logger(__name__) class CacheEntry: """Cache entry with TTL support.""" def __init__(self, value: Any, ttl: int): self.value = value self.created_at = time.time() self.ttl = ttl def is_expired(self) -> bool: """Check if cache entry is expired.""" if self.ttl <= 0: # Never expires return False return time.time() - self.created_at > self.ttl class CacheService: """Simple in-memory cache service with TTL support.""" def __init__(self): self.config = get_config().cache self._cache: Dict[str, CacheEntry] = {} self._lock = Lock() logger.info(f"Cache service initialized - enabled: {self.config.enabled}, TTL: {self.config.ttl}s, max_size: {self.config.max_size}") def _cleanup_expired(self) -> None: """Remove expired entries from cache.""" if not self.config.enabled: return current_time = time.time() expired_keys = [ key for key, entry in self._cache.items() if entry.is_expired() ] for key in expired_keys: del self._cache[key] if expired_keys: logger.debug(f"Cleaned up {len(expired_keys)} expired cache entries") def _evict_lru(self) -> None: """Evict least recently used entries if cache is full.""" if len(self._cache) <= self.config.max_size: return # Simple LRU: remove oldest entries sorted_entries = sorted( self._cache.items(), key=lambda x: x[1].created_at ) entries_to_remove = len(self._cache) - self.config.max_size + 1 for i in range(entries_to_remove): key = sorted_entries[i][0] del self._cache[key] logger.debug(f"Evicted {entries_to_remove} LRU cache entries") def get(self, key: str) -> Optional[Any]: """Get value from cache.""" if not self.config.enabled: return None with self._lock: self._cleanup_expired() entry = self._cache.get(key) if entry is None: logger.debug(f"Cache miss: {key}") return None if entry.is_expired(): del self._cache[key] logger.debug(f"Cache expired: {key}") return None logger.debug(f"Cache hit: {key}") return entry.value def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None: """Set value in cache.""" if not self.config.enabled: return if ttl is None: ttl = self.config.ttl with self._lock: self._cleanup_expired() self._evict_lru() self._cache[key] = CacheEntry(value, ttl) logger.debug(f"Cache set: {key} (TTL: {ttl}s)") def delete(self, key: str) -> bool: """Delete value from cache.""" if not self.config.enabled: return False with self._lock: if key in self._cache: del self._cache[key] logger.debug(f"Cache delete: {key}") return True return False def clear(self) -> None: """Clear all cache entries.""" with self._lock: count = len(self._cache) self._cache.clear() logger.info(f"Cache cleared: {count} entries removed") def stats(self) -> Dict[str, Union[int, bool]]: """Get cache statistics.""" with self._lock: self._cleanup_expired() return { "enabled": self.config.enabled, "size": len(self._cache), "max_size": self.config.max_size, "ttl": self.config.ttl, } def generate_key(self, *args, **kwargs) -> str: """Generate cache key from arguments.""" key_parts = [] # Add positional arguments for arg in args: if isinstance(arg, (str, int, float, bool)): key_parts.append(str(arg)) elif isinstance(arg, (list, tuple)): key_parts.append(str(sorted(arg) if isinstance(arg, list) else arg)) elif isinstance(arg, dict): key_parts.append(str(sorted(arg.items()))) else: key_parts.append(str(arg)) # Add keyword arguments for k, v in sorted(kwargs.items()): if isinstance(v, (str, int, float, bool)): key_parts.append(f"{k}:{v}") elif isinstance(v, (list, tuple)): key_parts.append(f"{k}:{sorted(v) if isinstance(v, list) else v}") elif isinstance(v, dict): key_parts.append(f"{k}:{sorted(v.items())}") else: key_parts.append(f"{k}:{v}") return "|".join(key_parts) # Global cache service instance _cache_service: Optional[CacheService] = None def get_cache_service() -> CacheService: """Get or create global cache service instance.""" global _cache_service if _cache_service is None: _cache_service = CacheService() return _cache_service

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vzeman/odoo-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server