"""Shared translation cache utilities.""" from __future__ import annotations import hashlib import logging from typing import Mapping, Optional import redis from config.env_config import REDIS_CONFIG logger = logging.getLogger(__name__) class TranslationCache: """Redis-backed cache shared by all translation capabilities.""" def __init__(self, config: Mapping[str, object]) -> None: self.ttl_seconds = int(config["ttl_seconds"]) self.sliding_expiration = bool(config["sliding_expiration"]) self.redis_client = self._init_redis_client() @property def available(self) -> bool: return self.redis_client is not None def build_key(self, *, model: str, target_lang: str, source_text: str) -> str: normalized_model = str(model or "").strip().lower() normalized_target_lang = str(target_lang or "").strip().lower() text = str(source_text or "") text_prefix = text[:4] digest = hashlib.sha256(text.encode("utf-8")).hexdigest() return f"trans:{normalized_model}:{normalized_target_lang}:{text_prefix}{digest}" def get(self, *, model: str, target_lang: str, source_text: str) -> Optional[str]: if self.redis_client is None: return None key = self.build_key(model=model, target_lang=target_lang, source_text=source_text) try: value = self.redis_client.get(key) logger.info( "Translation cache %s | model=%s target_lang=%s text_len=%s key=%s", "hit" if value is not None else "miss", model, target_lang, len(str(source_text or "")), key, ) if value and self.sliding_expiration: self.redis_client.expire(key, self.ttl_seconds) return value except Exception as exc: logger.warning("Redis get translation cache failed: %s", exc) return None def set(self, *, model: str, target_lang: str, source_text: str, translated_text: str) -> None: if self.redis_client is None: return key = self.build_key(model=model, target_lang=target_lang, source_text=source_text) try: self.redis_client.setex(key, self.ttl_seconds, translated_text) logger.info( "Translation cache write | model=%s target_lang=%s text_len=%s result_len=%s ttl_seconds=%s key=%s", model, target_lang, len(str(source_text or "")), len(str(translated_text or "")), self.ttl_seconds, key, ) except Exception as exc: logger.warning("Redis set translation cache failed: %s", exc) @staticmethod def _init_redis_client() -> Optional[redis.Redis]: try: client = redis.Redis( host=REDIS_CONFIG.get("host", "localhost"), port=REDIS_CONFIG.get("port", 6479), password=REDIS_CONFIG.get("password"), decode_responses=True, socket_timeout=REDIS_CONFIG.get("socket_timeout", 1), socket_connect_timeout=REDIS_CONFIG.get("socket_connect_timeout", 1), retry_on_timeout=REDIS_CONFIG.get("retry_on_timeout", False), health_check_interval=10, ) client.ping() return client except Exception as exc: logger.warning("Failed to initialize translation redis cache: %s", exc) return None