Files
LMS/backend/app/redis_client.py
T

76 lines
2.2 KiB
Python

"""
Redis helper — returns a connected client or None when Redis is unavailable.
Key namespace design (prevents cross-file / cross-user data leakage):
ann:{pdf_id}:{page_number}:{user_id}
Every segment is mandatory, so:
- User A opening file 1 never touches User A's data on file 2
- User A opening file 1 never touches User B's data on file 1
- Data for page 3 never affects page 7
TTL: 24 h — temp entry expires automatically if the user never returns.
On permanent save the entry is deleted immediately.
"""
import logging
import os
from typing import Optional
import redis as redis_lib
logger = logging.getLogger(__name__)
_client: Optional[redis_lib.Redis] = None
_warned = False # log the "unavailable" warning only once
def get_redis() -> Optional[redis_lib.Redis]:
"""Return a live Redis client, or None if Redis is unreachable."""
global _client, _warned
if _client is not None:
try:
_client.ping()
return _client
except Exception:
_client = None # connection dropped — try to reconnect below
url = os.getenv("REDIS_URL", "redis://localhost:6379/0")
try:
r: redis_lib.Redis = redis_lib.from_url(
url,
decode_responses=True,
socket_connect_timeout=2,
socket_timeout=2,
)
r.ping()
_client = r
_warned = False
logger.info("Redis connected: %s", url)
return _client
except Exception as exc:
if not _warned:
logger.warning(
"Redis unavailable (%s) — temp annotation cache disabled. "
"Set REDIS_URL or start a local Redis instance to enable it.",
exc,
)
_warned = True
return None
# Key helpers — centralised so there's one place to change the format.
ANN_TTL = 86_400 # 24 hours
def ann_temp_key(pdf_id: int, page_number: int, user_id: int) -> str:
"""Fully-namespaced Redis key for one user's unsaved canvas on one PDF page."""
return f"ann:{pdf_id}:{page_number}:{user_id}"
def ann_temp_page_pattern(pdf_id: int, page_number: int) -> str:
"""Glob pattern to list all users' temp entries for a given page."""
return f"ann:{pdf_id}:{page_number}:*"