169 lines
5.8 KiB
Python
169 lines
5.8 KiB
Python
import os
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
import redis
|
|
|
|
from modules.logger_tool import initialise_logger
|
|
|
|
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _redis_client() -> redis.Redis:
|
|
host = os.getenv('REDIS_HOST', '127.0.0.1')
|
|
port = int(os.getenv('REDIS_PORT', '6379'))
|
|
return redis.Redis(host=host, port=port, decode_responses=True)
|
|
|
|
|
|
@router.get("/ping")
|
|
def ping() -> Dict[str, Any]:
|
|
try:
|
|
r = _redis_client()
|
|
pong = r.ping()
|
|
return {"ok": True, "pong": pong}
|
|
except Exception as e:
|
|
logger.error(f"Redis ping failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis ping failed: {e}")
|
|
|
|
|
|
@router.get("/info")
|
|
def info(section: Optional[str] = Query(None, description="Optional INFO section, e.g. memory, server, clients, keyspace")) -> Dict[str, Any]:
|
|
try:
|
|
r = _redis_client()
|
|
data = r.info(section) if section else r.info()
|
|
return {"ok": True, "info": data}
|
|
except Exception as e:
|
|
logger.error(f"Redis info failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis info failed: {e}")
|
|
|
|
|
|
@router.get("/scan")
|
|
def scan(cursor: int = 0, pattern: Optional[str] = None, count: int = Query(100, ge=1, le=10000)) -> Dict[str, Any]:
|
|
try:
|
|
r = _redis_client()
|
|
next_cursor, keys = r.scan(cursor=cursor, match=pattern, count=count)
|
|
return {"ok": True, "cursor": next_cursor, "keys": keys, "count": len(keys)}
|
|
except Exception as e:
|
|
logger.error(f"Redis scan failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis scan failed: {e}")
|
|
|
|
|
|
@router.get("/keys")
|
|
def list_keys(pattern: str = Query("*", description="Glob-style pattern to match keys"), limit: int = Query(200, ge=1, le=5000)) -> Dict[str, Any]:
|
|
try:
|
|
r = _redis_client()
|
|
keys: List[str] = []
|
|
cursor = 0
|
|
while True and len(keys) < limit:
|
|
cursor, batch = r.scan(cursor=cursor, match=pattern, count=min(1000, limit - len(keys)))
|
|
keys.extend(batch)
|
|
if cursor == 0:
|
|
break
|
|
return {"ok": True, "keys": keys[:limit], "count": len(keys[:limit])}
|
|
except Exception as e:
|
|
logger.error(f"Redis keys failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis keys failed: {e}")
|
|
|
|
|
|
@router.get("/key")
|
|
def get_key(key: str) -> Dict[str, Any]:
|
|
try:
|
|
r = _redis_client()
|
|
t = r.type(key)
|
|
value: Any = None
|
|
if t == 'string':
|
|
value = r.get(key)
|
|
elif t == 'hash':
|
|
value = r.hgetall(key)
|
|
elif t == 'list':
|
|
value = r.lrange(key, 0, 99)
|
|
elif t == 'set':
|
|
value = list(r.smembers(key))
|
|
elif t == 'zset':
|
|
value = r.zrange(key, 0, 99, withscores=True)
|
|
ttl = r.ttl(key)
|
|
return {"ok": True, "type": t, "ttl": ttl, "value": value}
|
|
except Exception as e:
|
|
logger.error(f"Redis get key failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis get key failed: {e}")
|
|
|
|
|
|
class DeleteKeysBody(BaseModel):
|
|
keys: Optional[List[str]] = None
|
|
pattern: Optional[str] = None
|
|
|
|
|
|
@router.post("/delete")
|
|
def delete_keys(body: DeleteKeysBody) -> Dict[str, Any]:
|
|
if not body.keys and not body.pattern:
|
|
raise HTTPException(status_code=400, detail="Provide 'keys' or 'pattern'")
|
|
try:
|
|
r = _redis_client()
|
|
to_delete: List[str] = body.keys or []
|
|
if body.pattern:
|
|
# Resolve pattern to keys via SCAN to avoid blocking
|
|
keys: List[str] = []
|
|
cursor = 0
|
|
while True:
|
|
cursor, batch = r.scan(cursor=cursor, match=body.pattern, count=1000)
|
|
keys.extend(batch)
|
|
if cursor == 0:
|
|
break
|
|
to_delete.extend(keys)
|
|
deleted = 0
|
|
for k in set(to_delete):
|
|
try:
|
|
deleted += r.delete(k)
|
|
except Exception:
|
|
pass
|
|
return {"ok": True, "deleted": deleted}
|
|
except Exception as e:
|
|
logger.error(f"Redis delete failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis delete failed: {e}")
|
|
|
|
|
|
@router.post("/flushdb")
|
|
def flush_db(confirm: bool = Query(False, description="Must be true to flush the current DB")) -> Dict[str, Any]:
|
|
if not confirm:
|
|
raise HTTPException(status_code=400, detail="Set confirm=true to flush the current Redis DB")
|
|
try:
|
|
r = _redis_client()
|
|
r.flushdb()
|
|
return {"ok": True, "message": "Flushed current Redis DB"}
|
|
except Exception as e:
|
|
logger.error(f"Redis FLUSHDB failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Redis FLUSHDB failed: {e}")
|
|
|
|
|
|
@router.get("/queues")
|
|
def queue_stats() -> Dict[str, Any]:
|
|
"""Report queue sizes and basic counters used by DocumentProcessingQueue."""
|
|
try:
|
|
r = _redis_client()
|
|
keys = {
|
|
"high": "queue:high",
|
|
"normal": "queue:normal",
|
|
"low": "queue:low",
|
|
"processing": "processing",
|
|
"dead_letter": "dead_letter",
|
|
"metrics": "metrics",
|
|
}
|
|
data = {
|
|
"queues": {
|
|
"high": r.llen(keys["high"]),
|
|
"normal": r.llen(keys["normal"]),
|
|
"low": r.llen(keys["low"]),
|
|
},
|
|
"processing": r.hgetall(keys["processing"]),
|
|
"dead_letter": r.llen(keys["dead_letter"]),
|
|
"metrics": r.hgetall(keys["metrics"]),
|
|
}
|
|
return {"ok": True, **data}
|
|
except Exception as e:
|
|
logger.error(f"Queue stats failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Queue stats failed: {e}")
|
|
|