Files
ai_agent/backend/app/clients/redis.py
T
furyhawk 8351e73d39 feat: add Zustand stores for conversation, file preview, sidebar, theme, and knowledge base selection
- Implemented `conversation-store` for managing conversations and messages.
- Created `file-preview-store` to handle file preview state.
- Added `sidebar-store` for sidebar visibility management.
- Developed `theme-store` for theme persistence and management.
- Introduced `kb-selection-store` for managing active knowledge base selections with persistence.

chore: define API and chat types

- Added types for API responses, authentication, chat messages, conversations, and projects.
- Defined interfaces for various entities including users, sessions, and message ratings.

build: configure TypeScript and testing setup

- Set up `tsconfig.json` for TypeScript configuration.
- Created `vitest.config.ts` for testing configuration with Vitest.
- Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries.
- Configured Vercel deployment settings in `vercel.json`.
2026-06-11 16:54:43 +08:00

85 lines
2.6 KiB
Python

"""Redis client wrapper.
Provides a class-based Redis client for connection management and operations.
"""
from redis import asyncio as aioredis
from app.core.config import settings
class RedisClient:
"""Redis client wrapper for connection lifecycle management.
Usage in FastAPI lifespan:
async with contextmanager():
redis = RedisClient(settings.REDIS_URL)
await redis.connect()
yield {"redis": redis}
await redis.close()
"""
def __init__(self, url: str | None = None):
self.url = url or settings.REDIS_URL
self.client: aioredis.Redis | None = None
async def connect(self) -> None:
"""Connect to Redis server."""
self.client = aioredis.from_url( # type: ignore[no-untyped-call]
self.url,
encoding="utf-8",
decode_responses=True,
)
async def close(self) -> None:
"""Close Redis connection."""
if self.client:
await self.client.close()
self.client = None
async def get(self, key: str) -> str | None:
"""Get a value by key."""
if not self.client:
raise RuntimeError("Redis client not connected")
return await self.client.get(key) # type: ignore[no-any-return]
async def set(
self,
key: str,
value: str,
ttl: int | None = None,
) -> None:
"""Set a value with optional TTL (in seconds)."""
if not self.client:
raise RuntimeError("Redis client not connected")
await self.client.set(key, value, ex=ttl)
async def delete(self, key: str) -> int:
"""Delete a key. Returns number of keys deleted."""
if not self.client:
raise RuntimeError("Redis client not connected")
return await self.client.delete(key) # type: ignore[no-any-return]
async def exists(self, key: str) -> bool:
"""Check if key exists."""
if not self.client:
raise RuntimeError("Redis client not connected")
return bool(await self.client.exists(key))
async def ping(self) -> bool:
"""Ping Redis server. Returns True if connected."""
if not self.client:
return False
try:
await self.client.ping()
return True
except Exception:
return False
@property
def raw(self) -> aioredis.Redis:
"""Access the underlying aioredis client for advanced operations."""
if not self.client:
raise RuntimeError("Redis client not connected")
return self.client