This commit is contained in:
160
src/cache_manager.py
Normal file
160
src/cache_manager.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"""Caching system for Code Pattern Search CLI."""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Optional
|
||||||
|
import diskcache as dc
|
||||||
|
|
||||||
|
|
||||||
|
class CacheManager:
|
||||||
|
"""Manager for caching API responses and search results."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
cache_dir: Optional[Path] = None,
|
||||||
|
ttl: int = 3600,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the cache manager."""
|
||||||
|
if cache_dir is None:
|
||||||
|
cache_dir = Path.home() / ".cache" / "code-pattern-search"
|
||||||
|
|
||||||
|
self.cache_dir = Path(cache_dir)
|
||||||
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.ttl = ttl
|
||||||
|
self._cache: Optional[dc.Cache] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cache(self) -> dc.Cache:
|
||||||
|
"""Get or create the cache instance."""
|
||||||
|
if self._cache is None:
|
||||||
|
self._cache = dc.Cache(
|
||||||
|
str(self.cache_dir),
|
||||||
|
ttl=self.ttl,
|
||||||
|
disk_min_file_size=1024,
|
||||||
|
)
|
||||||
|
return self._cache
|
||||||
|
|
||||||
|
def _generate_key(self, key: str) -> str:
|
||||||
|
"""Generate a cache key from a string."""
|
||||||
|
return hashlib.sha256(key.encode()).hexdigest()
|
||||||
|
|
||||||
|
def get(self, key: str) -> Optional[Any]:
|
||||||
|
"""Get a value from the cache."""
|
||||||
|
try:
|
||||||
|
cache_key = self._generate_key(key)
|
||||||
|
return self.cache.get(cache_key)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set(self, key: str, value: Any) -> bool:
|
||||||
|
"""Set a value in the cache."""
|
||||||
|
try:
|
||||||
|
cache_key = self._generate_key(key)
|
||||||
|
self.cache.set(cache_key, value, expire=self.ttl)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete(self, key: str) -> bool:
|
||||||
|
"""Delete a value from the cache."""
|
||||||
|
try:
|
||||||
|
cache_key = self._generate_key(key)
|
||||||
|
del self.cache[cache_key]
|
||||||
|
return True
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear(self) -> bool:
|
||||||
|
"""Clear all cached data."""
|
||||||
|
try:
|
||||||
|
self.cache.clear()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_stats(self) -> dict[str, Any]:
|
||||||
|
"""Get cache statistics."""
|
||||||
|
try:
|
||||||
|
stats = self.cache.stats()
|
||||||
|
return {
|
||||||
|
"size": stats["size"],
|
||||||
|
"hits": stats["hits"],
|
||||||
|
"misses": stats["misses"],
|
||||||
|
"cache_size_mb": self._get_cache_size() / (1024 * 1024),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
return {
|
||||||
|
"size": 0,
|
||||||
|
"hits": 0,
|
||||||
|
"misses": 0,
|
||||||
|
"cache_size_mb": 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_cache_size(self) -> int:
|
||||||
|
"""Get total size of cache in bytes."""
|
||||||
|
total = 0
|
||||||
|
if self.cache_dir.exists():
|
||||||
|
for path in self.cache_dir.rglob("*"):
|
||||||
|
if path.is_file():
|
||||||
|
total += path.stat().st_size
|
||||||
|
return total
|
||||||
|
|
||||||
|
def get_all(self) -> dict[str, Any]:
|
||||||
|
"""Get all cached entries."""
|
||||||
|
try:
|
||||||
|
entries = {}
|
||||||
|
for key in self.cache:
|
||||||
|
try:
|
||||||
|
value = self.cache[key]
|
||||||
|
entries[key] = value
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return entries
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_by_prefix(self, prefix: str) -> dict[str, Any]:
|
||||||
|
"""Get cached entries with a specific prefix."""
|
||||||
|
cache_key_prefix = self._generate_key(prefix)
|
||||||
|
entries = {}
|
||||||
|
|
||||||
|
for key in self.cache:
|
||||||
|
if key.startswith(cache_key_prefix):
|
||||||
|
try:
|
||||||
|
value = self.cache[key]
|
||||||
|
entries[key] = value
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def cleanup(self) -> int:
|
||||||
|
"""Remove expired cache entries."""
|
||||||
|
try:
|
||||||
|
removed = 0
|
||||||
|
for key in list(self.cache):
|
||||||
|
try:
|
||||||
|
if self.cache.get(key, default=dc.NOTSET) is dc.NOTSET:
|
||||||
|
del self.cache[key]
|
||||||
|
removed += 1
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return removed
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
"""Close the cache connection."""
|
||||||
|
if self._cache is not None:
|
||||||
|
self._cache.close()
|
||||||
|
self._cache = None
|
||||||
|
|
||||||
|
def __enter__(self) -> "CacheManager":
|
||||||
|
"""Context manager entry."""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||||
|
"""Context manager exit."""
|
||||||
|
self.close()
|
||||||
Reference in New Issue
Block a user