Spaces:
Running
Running
| """Tiny in-process TTL cache for read-path responses. | |
| The cloud backend talks to Turso / Qdrant / R2 across regions; every call is | |
| an HTTP round-trip. The read endpoints (book list, book detail, status | |
| counts) are recomputed on every navigation even though the underlying data | |
| barely changes between clicks β that's the bulk of the "moving between pages | |
| is slow" latency the owner reported. | |
| A short-TTL in-memory cache collapses repeat reads to a single process-local | |
| lookup. The HF Space runs a single replica, so a process-local dict is | |
| correct; user-facing writes (create / patch / delete book, label edits) | |
| invalidate by key or key-prefix so a read never serves data the same user | |
| just changed. The TTL is the backstop for changes that happen outside the | |
| request path (e.g. a pipeline job flipping a book's status) β those become | |
| visible within ``DEFAULT_TTL`` seconds. | |
| Keep it dumb: no LRU eviction (the key space is tiny β a handful of books Γ | |
| filter combinations), no async, no cross-process coherence. | |
| """ | |
| from __future__ import annotations | |
| import threading | |
| import time | |
| from typing import Callable, TypeVar | |
| DEFAULT_TTL = 30.0 # seconds | |
| _T = TypeVar("_T") | |
| class _Entry: | |
| __slots__ = ("value", "expires") | |
| def __init__(self, value: object, expires: float) -> None: | |
| self.value = value | |
| self.expires = expires | |
| _store: dict[str, _Entry] = {} | |
| _lock = threading.Lock() | |
| def get_or_set(key: str, producer: Callable[[], _T], *, ttl: float = DEFAULT_TTL) -> _T: | |
| """Return the cached value for ``key`` or compute, store, and return it. | |
| ``producer`` runs OUTSIDE the lock so concurrent misses don't serialize | |
| behind one another's DB calls (a brief duplicate-compute on a cold key is | |
| cheaper than holding the lock across a cross-region round-trip). | |
| """ | |
| now = time.monotonic() | |
| with _lock: | |
| e = _store.get(key) | |
| if e is not None and e.expires > now: | |
| return e.value # type: ignore[return-value] | |
| value = producer() | |
| with _lock: | |
| _store[key] = _Entry(value, time.monotonic() + ttl) | |
| return value | |
| def invalidate(key: str) -> None: | |
| with _lock: | |
| _store.pop(key, None) | |
| def invalidate_prefix(prefix: str) -> None: | |
| with _lock: | |
| for k in [k for k in _store if k.startswith(prefix)]: | |
| _store.pop(k, None) | |
| def clear() -> None: | |
| """Drop everything β test hook + a hard reset for cache-coherence bugs.""" | |
| with _lock: | |
| _store.clear() | |