"""Pluggable session cache abstraction. This module defines a cache interface for authenticated sessions and a default process-local in-memory implementation. The backend can swap the cache implementation without changing the authentication dependency logic. """ from __future__ import annotations import time from typing import TYPE_CHECKING, Protocol if TYPE_CHECKING: # pragma: no cover from app.models.auth import Session class SessionCache(Protocol): """Interface for session token validation cache backends.""" def get(self, token: str) -> Session | None: """Return the cached session for *token*, or ``None`` if missing.""" def set(self, token: str, session: Session, ttl_seconds: float) -> None: """Cache the validated *session* for *token* for *ttl_seconds*.""" def invalidate(self, token: str) -> None: """Remove *token* from the cache if it exists.""" def clear(self) -> None: """Remove all entries from the cache.""" class InMemorySessionCache: """A process-local session cache implementation.""" def __init__(self) -> None: self._entries: dict[str, tuple[Session, float]] = {} def get(self, token: str) -> Session | None: entry = self._entries.get(token) if entry is None: return None session, expires_at = entry if time.monotonic() >= expires_at: self._entries.pop(token, None) return None return session def set(self, token: str, session: Session, ttl_seconds: float) -> None: self._entries[token] = (session, time.monotonic() + ttl_seconds) def invalidate(self, token: str) -> None: self._entries.pop(token, None) def clear(self) -> None: self._entries.clear() class NoOpSessionCache: """A no-op session cache used when caching is disabled.""" def get(self, token: str) -> Session | None: return None def set(self, token: str, session: Session, ttl_seconds: float) -> None: return None def invalidate(self, token: str) -> None: return None def clear(self) -> None: return None