latest
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
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}")
|
||||
|
||||
Reference in New Issue
Block a user