Files
vault-dash/app/services/cache.py
Bu5hm4nn 887565be74 fix(types): resolve all mypy type errors (CORE-003)
- Fix return type annotation for get_default_premium_for_product
- Add type narrowing for Weight|Money union using _as_money helper
- Add isinstance checks before float() calls for object types
- Add type guard for Decimal.exponent comparison
- Use _unit_typed and _currency_typed properties for type narrowing
- Cast option_type to OptionType Literal after validation
- Fix provider type hierarchy in backtesting services
- Add types-requests to dev dependencies
- Remove '|| true' from CI type-check job

All 36 mypy errors resolved across 15 files.
2026-03-30 00:05:09 +02:00

89 lines
2.7 KiB
Python

"""Redis-backed caching utilities with graceful fallback support."""
from __future__ import annotations
import json
import logging
from datetime import datetime
from typing import Any
logger = logging.getLogger(__name__)
try:
from redis.asyncio import Redis as RedisClient
except ImportError: # pragma: no cover - optional dependency
RedisClient = None # type: ignore[misc,assignment]
class CacheService:
"""Small async cache wrapper around Redis."""
def __init__(self, url: str | None, default_ttl: int = 300) -> None:
self.url = url
self.default_ttl = default_ttl
self._client: RedisClient | None = None
self._enabled = bool(url and RedisClient)
@property
def enabled(self) -> bool:
return self._enabled and self._client is not None
async def connect(self) -> None:
if not self._enabled:
if self.url and RedisClient is None:
logger.warning("Redis URL configured but redis package is not installed; cache disabled")
return
try:
if self.url:
self._client = RedisClient.from_url(self.url, decode_responses=True) # type: ignore[misc]
await self._client.ping() # type: ignore[union-attr]
logger.info("Connected to Redis cache")
except Exception as exc: # pragma: no cover - network dependent
logger.warning("Redis unavailable, cache disabled: %s", exc)
self._client = None
async def close(self) -> None:
if self._client is None:
return
await self._client.aclose()
self._client = None
async def get_json(self, key: str) -> dict[str, Any] | list[Any] | None:
if self._client is None:
return None
value = await self._client.get(key)
if value is None:
return None
return json.loads(value)
async def set_json(self, key: str, value: Any, ttl: int | None = None) -> None:
if self._client is None:
return
payload = json.dumps(value, default=self._json_default)
await self._client.set(key, payload, ex=ttl or self.default_ttl)
@staticmethod
def _json_default(value: Any) -> str:
if isinstance(value, datetime):
return value.isoformat()
raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")
# Global cache instance
_cache_instance: CacheService | None = None
def get_cache() -> CacheService:
"""Get or create global cache instance."""
global _cache_instance
if _cache_instance is None:
import os
redis_url = os.environ.get("REDIS_URL")
_cache_instance = CacheService(redis_url)
return _cache_instance