"""Cache module."""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any, Dict, Optional
JsonDict = Dict[str, Any]
[docs]
@dataclass
class CacheEntry:
"""Cache entry."""
value: Any
expires_at: Optional[datetime] = None
metadata: Optional[JsonDict] = None
[docs]
@dataclass
class CacheConfig:
"""Cache configuration."""
ttl: Optional[int] = None
max_size: Optional[int] = None
[docs]
class Cache(ABC):
"""Cache interface."""
[docs]
@abstractmethod
def get(self, key: str) -> Optional[CacheEntry]:
"""Get value from cache.
Args:
key: Cache key.
Returns:
Cache entry or None if not found.
"""
...
[docs]
@abstractmethod
def set(
self,
key: str,
value: Any,
expires_at: Optional[datetime] = None,
metadata: Optional[JsonDict] = None,
) -> CacheEntry:
"""Set value in cache.
Args:
key: Cache key.
value: Value to cache.
expires_at: Expiration time.
metadata: Additional metadata.
Returns:
Cache entry.
"""
...
[docs]
@abstractmethod
def delete(self, key: str) -> Optional[CacheEntry]:
"""Delete value from cache.
Args:
key: Cache key.
Returns:
Deleted cache entry or None if not found.
"""
...
[docs]
@abstractmethod
def clear(self) -> None:
"""Clear cache."""
...
[docs]
class MemoryCache(Cache):
"""Memory cache implementation."""
[docs]
def __init__(self, config: Optional[CacheConfig] = None) -> None:
"""Initialize memory cache.
Args:
config: Cache configuration.
"""
self.config = config or CacheConfig()
self._cache: Dict[str, CacheEntry] = {}
[docs]
def get(self, key: str) -> Optional[CacheEntry]:
"""Get value from cache.
Args:
key: Cache key.
Returns:
Cache entry or None if not found.
"""
entry = self._cache.get(key)
if entry is None:
return None
if entry.expires_at and entry.expires_at <= datetime.now():
del self._cache[key]
return None
return entry
[docs]
def set(
self,
key: str,
value: Any,
expires_at: Optional[datetime] = None,
metadata: Optional[JsonDict] = None,
) -> CacheEntry:
"""Set value in cache.
Args:
key: Cache key.
value: Value to cache.
expires_at: Expiration time.
metadata: Additional metadata.
Returns:
Cache entry.
"""
if self.config.max_size and len(self._cache) >= self.config.max_size:
# Remove oldest entry
oldest_key = next(iter(self._cache))
del self._cache[oldest_key]
if self.config.ttl and not expires_at:
expires_at = datetime.now() + timedelta(seconds=self.config.ttl)
entry = CacheEntry(value=value, expires_at=expires_at, metadata=metadata)
self._cache[key] = entry
return entry
[docs]
def delete(self, key: str) -> Optional[CacheEntry]:
"""Delete value from cache.
Args:
key: Cache key.
Returns:
Deleted cache entry or None if not found.
"""
return self._cache.pop(key, None)
[docs]
def clear(self) -> None:
"""Clear cache."""
self._cache.clear()