from __future__ import annotations from collections import OrderedDict from dataclasses import dataclass from threading import Lock from time import monotonic from govoplan_core.core.access import PrincipalRef from govoplan_core.core.principal_cache import AuthPrincipalRevision @dataclass(frozen=True, slots=True) class CachedPrincipal: principal: PrincipalRef revision: AuthPrincipalRevision stored_at: float class PrincipalSummaryCache: """A bounded process-local cache containing no ORM or secret objects.""" def __init__(self) -> None: self._entries: OrderedDict[str, CachedPrincipal] = OrderedDict() self._lock = Lock() def get( self, token_digest: str, *, session_ttl_seconds: int, api_key_ttl_seconds: int, ) -> CachedPrincipal | None: with self._lock: entry = self._entries.get(token_digest) if entry is None: return None ttl = ( api_key_ttl_seconds if entry.principal.auth_method == "api_key" else session_ttl_seconds ) if ttl <= 0 or monotonic() - entry.stored_at > ttl: self._entries.pop(token_digest, None) return None self._entries.move_to_end(token_digest) return entry def put( self, token_digest: str, *, principal: PrincipalRef, revision: AuthPrincipalRevision, max_entries: int, ) -> None: with self._lock: self._entries[token_digest] = CachedPrincipal( principal=principal, revision=revision, stored_at=monotonic(), ) self._entries.move_to_end(token_digest) while len(self._entries) > max(1, max_entries): self._entries.popitem(last=False) def discard(self, token_digest: str) -> None: with self._lock: self._entries.pop(token_digest, None) def clear(self) -> None: with self._lock: self._entries.clear() principal_summary_cache = PrincipalSummaryCache() __all__ = [ "CachedPrincipal", "PrincipalSummaryCache", "principal_summary_cache", ]