topvenues-explorer / src /cache.py
sidneibarbieri's picture
Upload 46 files
fafbad3 verified
Raw
History Blame Contribute Delete
2.41 kB
"""Local file-based cache for abstracts."""
import hashlib
import json
from datetime import datetime, timedelta
from pathlib import Path
from .models import CacheEntry
class CacheManager:
"""Persists abstracts on disk with TTL expiry."""
def __init__(self, cache_dir: Path, enabled: bool = True, ttl_hours: int = 168):
self.cache_dir = Path(cache_dir)
self.enabled = enabled
self.ttl = timedelta(hours=ttl_hours)
self.hits = 0
self.misses = 0
if self.enabled:
self.cache_dir.mkdir(parents=True, exist_ok=True)
def get(self, key: str) -> str | None:
if not self.enabled:
return None
cache_path = self._cache_path(key)
if not cache_path.exists():
self.misses += 1
return None
with open(cache_path, encoding="utf-8") as fh:
entry = CacheEntry(**json.load(fh))
if entry.expires_at and datetime.now() > entry.expires_at:
cache_path.unlink()
self.misses += 1
return None
entry.access_count += 1
entry.last_accessed = datetime.now()
self._write_entry(cache_path, entry)
self.hits += 1
return entry.value
def set(self, key: str, value: str) -> None:
if not self.enabled:
return
entry = CacheEntry(
key=key,
value=value,
expires_at=datetime.now() + self.ttl if self.ttl else None,
)
self._write_entry(self._cache_path(key), entry)
def clear(self) -> int:
if not self.cache_dir.exists():
return 0
count = sum(1 for f in self.cache_dir.glob("*.json") if f.unlink() is None)
self.hits = self.misses = 0
return count
def get_stats(self) -> dict:
total = self.hits + self.misses
return {
"enabled": self.enabled,
"hits": self.hits,
"misses": self.misses,
"hit_rate": self.hits / total if total else 0,
"cache_dir": str(self.cache_dir),
}
def _cache_path(self, key: str) -> Path:
return self.cache_dir / f"{hashlib.md5(key.encode()).hexdigest()}.json"
def _write_entry(self, path: Path, entry: CacheEntry) -> None:
with open(path, "w", encoding="utf-8") as fh:
json.dump(entry.model_dump(), fh, default=str, indent=2)