Files
dienakdz 87f2845483 Refactor code for improved readability and consistency
- Cleaned up whitespace and formatting in various files including http.py, language.py, logger.py, safe_exec.py, and SQL migration scripts.
- Consolidated import statements and removed unnecessary blank lines.
- Updated logging configuration for better clarity.
- Enhanced the safe execution code with improved error handling and logging.
- Removed commented-out code and unnecessary variables in backfill_zero_trades.py and other scripts.
- Added a pyproject.toml for Ruff and Vulture configuration.
- Introduced requirements-dev.txt for development dependencies.
- Removed commented-out stock entries in init.sql for cleaner migration scripts.
2026-04-09 14:30:51 +07:00

130 lines
3.6 KiB
Python

"""
Cache utilities.
Local-first behavior: use in-memory cache by default.
Redis is only used when explicitly enabled via environment variables.
"""
import json
import threading
import time
from typing import Any, Optional
from app.config import CacheConfig
from app.utils.logger import get_logger
logger = get_logger(__name__)
class MemoryCache:
"""In-memory cache (an alternative if Redis is unavailable)"""
def __init__(self):
self._cache = {}
self._lock = threading.Lock()
def get(self, key: str) -> Optional[str]:
with self._lock:
if key in self._cache:
data, expiry = self._cache[key]
if expiry > time.time():
return data
else:
del self._cache[key]
return None
def setex(self, key: str, ttl: int, value: str):
with self._lock:
expiry = time.time() + ttl
self._cache[key] = (value, expiry)
def delete(self, key: str):
with self._lock:
if key in self._cache:
del self._cache[key]
def clear(self):
with self._lock:
self._cache.clear()
class CacheManager:
"""cache manager"""
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self._client = None
self._use_redis = False
# Local-first: do NOT touch Redis unless explicitly enabled.
if not CacheConfig.ENABLED:
self._client = MemoryCache()
self._use_redis = False
return
# Try Redis only when enabled.
try:
import redis
from app.config import RedisConfig
self._client = redis.Redis(
host=RedisConfig.HOST,
port=RedisConfig.PORT,
db=RedisConfig.DB,
password=RedisConfig.PASSWORD,
decode_responses=True,
socket_connect_timeout=RedisConfig.CONNECT_TIMEOUT,
socket_timeout=RedisConfig.SOCKET_TIMEOUT,
)
self._client.ping()
self._use_redis = True
logger.info("Redis cache connected")
except Exception as e:
# Fall back silently (keep startup logs clean in local mode).
logger.info(f"Redis is enabled but unavailable; using in-memory cache instead: {e}")
self._client = MemoryCache()
self._use_redis = False
def get(self, key: str) -> Optional[Any]:
"""Get cache"""
try:
data = self._client.get(key)
if data:
return json.loads(data)
return None
except Exception as e:
logger.error(f"Cache read failed: {e}")
return None
def set(self, key: str, value: Any, ttl: int = 300):
"""Set up cache"""
try:
self._client.setex(key, ttl, json.dumps(value))
except Exception as e:
logger.error(f"Cache write failed: {e}")
def delete(self, key: str):
"""Delete cache"""
try:
self._client.delete(key)
except Exception as e:
logger.error(f"Cache delete failed: {e}")
@property
def is_redis(self) -> bool:
return self._use_redis