Files
api-copp/redis_db/__init__.py

123 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pydantic.generics import GenericModel
import os
import redis
class RedisSettings(GenericModel):
REDIS_HOST: str = os.getenv("REDIS_HOST", "192.168.1.19")
REDIS_PORT: int = int(os.getenv("REDIS_PORT", 6379))
REDIS_DB: int = int(os.getenv("REDIS_DB", 0))
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD") or None
@property
def REDIS_URL(self) -> str:
"""Получить Redis URL для подключения"""
if self.REDIS_PASSWORD:
return f"redis://:{self.REDIS_PASSWORD}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
else:
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
def test_connection(self) -> bool:
"""Протестировать подключение к Redis"""
try:
get_redis_client().ping()
print(f"✓ Успешно подключено к Redis на {self.REDIS_HOST}:{self.REDIS_PORT}")
return True
except redis.ConnectionError as e:
print(f"✗ Ошибка подключения к Redis: {e}")
return False
# Глобальная конфигурация
_redis_settings = RedisSettings()
# Единый connection pool на весь процесс (max_connections=20 — для FastAPI с несколькими воркерами)
_pool = redis.ConnectionPool(
host=_redis_settings.REDIS_HOST,
port=_redis_settings.REDIS_PORT,
db=_redis_settings.REDIS_DB,
password=_redis_settings.REDIS_PASSWORD,
decode_responses=True,
max_connections=20,
socket_connect_timeout=3,
socket_timeout=3,
retry_on_timeout=True,
)
def get_redis_settings() -> RedisSettings:
"""Получить конфигурацию Redis"""
return _redis_settings
def get_redis_client() -> redis.Redis:
"""Получить клиент из общего connection pool (не создаёт новое TCP-соединение)."""
return redis.Redis(connection_pool=_pool)
# ---------------------------------------------------------------------------
# Блэклист токенов
# ---------------------------------------------------------------------------
_BLACKLIST_PREFIX = "token_blacklist:"
def blacklist_token(jti: str, ttl_seconds: int) -> None:
"""Добавить jti токена в блэклист на ttl_seconds секунд."""
try:
client = get_redis_client()
client.setex(f"{_BLACKLIST_PREFIX}{jti}", ttl_seconds, "1")
except redis.RedisError:
pass # Redis недоступен — не блокируем работу
def is_token_blacklisted(jti: str) -> bool:
"""Проверить, отозван ли токен."""
try:
client = get_redis_client()
return client.exists(f"{_BLACKLIST_PREFIX}{jti}") > 0
except redis.RedisError:
return False # при ошибке Redis не блокируем пользователей
# ---------------------------------------------------------------------------
# Кэш
# ---------------------------------------------------------------------------
_CACHE_PREFIX = "cache:"
def cache_get(key: str) -> str | None:
"""Получить значение из кэша по ключу. Возвращает None если нет или Redis недоступен."""
try:
client = get_redis_client()
return client.get(f"{_CACHE_PREFIX}{key}")
except redis.RedisError:
return None
def cache_set(key: str, value: str, ttl_seconds: int = 300) -> None:
"""Сохранить строку в кэш с TTL (по умолчанию 5 минут)."""
try:
client = get_redis_client()
client.setex(f"{_CACHE_PREFIX}{key}", ttl_seconds, value)
except redis.RedisError:
pass
def cache_delete(key: str) -> None:
"""Удалить ключ из кэша."""
try:
client = get_redis_client()
client.delete(f"{_CACHE_PREFIX}{key}")
except redis.RedisError:
pass
__all__ = [
"RedisSettings", "get_redis_settings", "get_redis_client",
"blacklist_token", "is_token_blacklisted",
"cache_get", "cache_set", "cache_delete",
]