Spaces:
Sleeping
Sleeping
File size: 2,929 Bytes
db284b9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | """A tiny bounded LRU (Least-Recently-Used) cache backed by OrderedDict.
Why not functools.lru_cache? These caches are SHARED between two endpoints (the
blocking and the streaming answer handlers read *and* write the same store),
they need an explicit `cached` flag in the HTTP response, and they must support
a per-graph .clear() when the active graph is hot-swapped. A decorator gives you
none of those. ~25 lines of OrderedDict buys all of it with O(1) get/set/evict
and a hard memory ceiling, which is the whole point β the old plain-dict caches
grew without bound and leaked memory on a long-running server.
"""
from __future__ import annotations
from collections import OrderedDict
from typing import Generic, Optional, TypeVar
K = TypeVar("K")
V = TypeVar("V")
class LRUCache(Generic[K, V]):
"""Fixed-capacity cache that evicts the least-recently-used entry first.
The eviction policy IS the OrderedDict's ordering: we keep the *oldest*
(least-recently-used) entry on the left and the *most-recently-used* on the
right. Every read or write moves the touched key to the right, so whatever
has drifted to the far left is, by definition, the coldest entry β and that's
exactly what we drop when we're over capacity.
"""
def __init__(self, capacity: int = 256) -> None:
if capacity <= 0:
raise ValueError("LRUCache capacity must be positive")
self.capacity = capacity
self._store: "OrderedDict[K, V]" = OrderedDict()
def get(self, key: K) -> Optional[V]:
"""Return the value (and mark it most-recently-used), or None on miss."""
if key not in self._store:
return None
# A successful read counts as "use": promote the key to the right end so
# it survives the next eviction. This is what makes it LRU and not FIFO.
self._store.move_to_end(key)
return self._store[key]
def __contains__(self, key: K) -> bool:
# Note: membership does NOT promote β only get()/set() count as a "use".
return key in self._store
def set(self, key: K, value: V) -> None:
"""Insert/overwrite a value, then evict oldest entries past capacity."""
if key in self._store:
# Overwriting an existing key still counts as a use β promote it.
self._store.move_to_end(key)
self._store[key] = value
# Evict from the LEFT (oldest) until we're back within the ceiling.
# last=False pops the first-inserted / least-recently-used item; a normal
# popitem() would pop the newest, which is the opposite of what we want.
while len(self._store) > self.capacity:
self._store.popitem(last=False)
def clear(self) -> None:
"""Drop everything β used when the active graph changes (snippets differ)."""
self._store.clear()
def __len__(self) -> int:
return len(self._store)
|