Skip to main content
Glama
base.py8.01 kB
"""Base caching framework.""" import asyncio import hashlib import time from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Set from dataclasses import dataclass from ..utils.logging import get_logger logger = get_logger(__name__) @dataclass class CacheEntry: """Cache entry with metadata.""" key: str value: Any created_at: float accessed_at: float hit_count: int = 0 ttl: Optional[float] = None def is_expired(self) -> bool: """Check if entry is expired. Returns: True if expired """ if self.ttl is None: return False return time.time() > self.created_at + self.ttl def update_access(self) -> None: """Update access statistics.""" self.accessed_at = time.time() self.hit_count += 1 class BaseCache(ABC): """Base class for cache implementations.""" def __init__(self, name: str, max_size: int = 1000) -> None: """Initialize cache. Args: name: Cache name max_size: Maximum number of entries """ self.name = name self.max_size = max_size self.enabled = True self._stats = { "hits": 0, "misses": 0, "sets": 0, "deletes": 0, "evictions": 0, } self._logger = get_logger(f"{__name__}.{name}") @abstractmethod async def get(self, key: str) -> Optional[Any]: """Get value from cache. Args: key: Cache key Returns: Cached value or None """ pass @abstractmethod async def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None: """Set value in cache. Args: key: Cache key value: Value to cache ttl: Time to live in seconds """ pass @abstractmethod async def delete(self, key: str) -> bool: """Delete value from cache. Args: key: Cache key Returns: True if key existed """ pass @abstractmethod async def clear(self) -> None: """Clear all cache entries.""" pass @abstractmethod async def size(self) -> int: """Get current cache size. Returns: Number of entries in cache """ pass @abstractmethod async def keys(self) -> List[str]: """Get all cache keys. Returns: List of cache keys """ pass def get_stats(self) -> Dict[str, Any]: """Get cache statistics. Returns: Statistics dictionary """ total_requests = self._stats["hits"] + self._stats["misses"] hit_rate = (self._stats["hits"] / total_requests * 100) if total_requests > 0 else 0 return { **self._stats, "hit_rate": hit_rate, "total_requests": total_requests, "enabled": self.enabled, "max_size": self.max_size, } def reset_stats(self) -> None: """Reset cache statistics.""" for key in self._stats: self._stats[key] = 0 self._logger.info(f"Cache {self.name} statistics reset") def _record_hit(self) -> None: """Record cache hit.""" self._stats["hits"] += 1 def _record_miss(self) -> None: """Record cache miss.""" self._stats["misses"] += 1 def _record_set(self) -> None: """Record cache set.""" self._stats["sets"] += 1 def _record_delete(self) -> None: """Record cache delete.""" self._stats["deletes"] += 1 def _record_eviction(self) -> None: """Record cache eviction.""" self._stats["evictions"] += 1 class CacheManager: """Manager for multiple cache instances.""" def __init__(self) -> None: """Initialize cache manager.""" self.caches: Dict[str, BaseCache] = {} self._logger = get_logger(__name__) def add_cache(self, cache: BaseCache) -> None: """Add cache to manager. Args: cache: Cache instance to add """ self.caches[cache.name] = cache self._logger.info(f"Added cache: {cache.name}") def remove_cache(self, cache_name: str) -> None: """Remove cache from manager. Args: cache_name: Name of cache to remove """ if cache_name in self.caches: del self.caches[cache_name] self._logger.info(f"Removed cache: {cache_name}") def get_cache(self, cache_name: str) -> Optional[BaseCache]: """Get cache by name. Args: cache_name: Cache name Returns: Cache instance or None """ return self.caches.get(cache_name) async def clear_all(self) -> None: """Clear all caches.""" for cache in self.caches.values(): await cache.clear() self._logger.info("Cleared all caches") def get_all_stats(self) -> Dict[str, Dict[str, Any]]: """Get statistics for all caches. Returns: Dictionary mapping cache names to their statistics """ return {name: cache.get_stats() for name, cache in self.caches.items()} def reset_all_stats(self) -> None: """Reset statistics for all caches.""" for cache in self.caches.values(): cache.reset_stats() self._logger.info("Reset all cache statistics") def enable_all(self) -> None: """Enable all caches.""" for cache in self.caches.values(): cache.enabled = True self._logger.info("Enabled all caches") def disable_all(self) -> None: """Disable all caches.""" for cache in self.caches.values(): cache.enabled = False self._logger.info("Disabled all caches") def generate_cache_key(*args: Any, prefix: str = "") -> str: """Generate cache key from arguments. Args: *args: Arguments to include in key prefix: Optional prefix for key Returns: Generated cache key """ # Create string representation of all arguments key_parts = [str(arg) for arg in args] key_string = "|".join(key_parts) # Hash the key for consistent length and safety key_hash = hashlib.sha256(key_string.encode()).hexdigest()[:16] if prefix: return f"{prefix}:{key_hash}" return key_hash def cache_result(cache_name: str, ttl: Optional[float] = None, key_prefix: str = ""): """Decorator to cache function results. Args: cache_name: Name of cache to use ttl: Time to live for cached result key_prefix: Prefix for cache key Returns: Decorator function """ def decorator(func): async def wrapper(*args, **kwargs): # Get cache manager from first argument (should be server instance) cache_manager = None if args and hasattr(args[0], "cache_manager"): cache_manager = args[0].cache_manager if not cache_manager: # No cache available, execute function normally return await func(*args, **kwargs) cache = cache_manager.get_cache(cache_name) if not cache or not cache.enabled: # Cache not available or disabled return await func(*args, **kwargs) # Generate cache key cache_key = generate_cache_key(*args, **kwargs, prefix=key_prefix) # Try to get from cache cached_result = await cache.get(cache_key) if cached_result is not None: return cached_result # Execute function and cache result result = await func(*args, **kwargs) await cache.set(cache_key, result, ttl) return result return wrapper return decorator

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/namnd00/mcp-server-hero'

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