55 lines
1.5 KiB
Python
55 lines
1.5 KiB
Python
import time
|
|
from collections import OrderedDict
|
|
from dataclasses import dataclass
|
|
from typing import Any, Optional
|
|
|
|
|
|
@dataclass
|
|
class CacheItem:
|
|
value: Any
|
|
expires_at: Optional[float] = None
|
|
|
|
|
|
class Cache:
|
|
def __init__(self, max_size: int = 1000) -> None:
|
|
self.max_size = max_size
|
|
self._store: "OrderedDict[str, CacheItem]" = OrderedDict()
|
|
self._hits = 0
|
|
self._misses = 0
|
|
|
|
def get(self, key: str) -> Optional[Any]:
|
|
item = self._store.get(key)
|
|
if item is None:
|
|
self._misses += 1
|
|
return None
|
|
if item.expires_at and item.expires_at <= time.time():
|
|
self._store.pop(key, None)
|
|
self._misses += 1
|
|
return None
|
|
self._store.move_to_end(key)
|
|
self._hits += 1
|
|
return item.value
|
|
|
|
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
expires_at = time.time() + ttl if ttl else None
|
|
self._store[key] = CacheItem(value=value, expires_at=expires_at)
|
|
self._store.move_to_end(key)
|
|
self._evict()
|
|
|
|
def delete(self, key: str) -> None:
|
|
self._store.pop(key, None)
|
|
|
|
def clear(self) -> None:
|
|
self._store.clear()
|
|
|
|
def _evict(self) -> None:
|
|
while len(self._store) > self.max_size:
|
|
self._store.popitem(last=False)
|
|
|
|
def stats(self) -> dict[str, int]:
|
|
return {
|
|
"size": len(self._store),
|
|
"hits": self._hits,
|
|
"misses": self._misses,
|
|
}
|