Spaces:
Sleeping
Sleeping
Download app/dda/exception_routes.py from coderuday21/satdetect-dev: direct link, hf CLI and curl.
- Browser
- Download file 4.72 kB
-
https://huggingface.co/spaces/coderuday21/satdetect-dev/resolve/main/app/dda/exception_routes.py
- Command line
-
hf download hf://spaces/coderuday21/satdetect-dev/app/dda/exception_routes.py
-
curl -L -o exception_routes.py https://huggingface.co/spaces/coderuday21/satdetect-dev/resolve/main/app/dda/exception_routes.py
4.72 kB
| """API for the Exception Log page — system errors captured by the global | |
| FastAPI exception handlers in app/main.py.""" | |
| from __future__ import annotations | |
| from datetime import datetime, timedelta, timezone | |
| from typing import Optional | |
| from fastapi import APIRouter, Depends, HTTPException, Query | |
| from sqlalchemy.orm import Session | |
| from ..database import get_db | |
| from ..models import User | |
| from .rbac.permissions import require_module_permission | |
| from .tree.audit_service import isoformat_ist | |
| from .tree.models import ExceptionLog | |
| router = APIRouter() | |
| def _require_dda(): | |
| from .config import IS_DDA_MODE | |
| if not IS_DDA_MODE: | |
| raise HTTPException(status_code=404, detail="DDA mode is not enabled") | |
| def _entry_dict(entry: ExceptionLog, *, with_traceback: bool = False) -> dict: | |
| d = { | |
| "id": entry.id, | |
| "timestamp": isoformat_ist(entry.created_at), | |
| "type": entry.exception_type, | |
| "message": entry.message or "", | |
| "path": entry.path or "", | |
| "method": entry.method or "", | |
| "statusCode": entry.status_code, | |
| "handled": bool(entry.handled), | |
| "userEmail": entry.user_email or "", | |
| } | |
| if with_traceback: | |
| d["traceback"] = entry.traceback or "" | |
| return d | |
| def _parse_date(value: Optional[str], *, end_of_day: bool = False) -> Optional[datetime]: | |
| if not value: | |
| return None | |
| try: | |
| dt = datetime.fromisoformat(value) | |
| except ValueError: | |
| raise HTTPException(status_code=400, detail=f"Invalid date: {value!r} (expected YYYY-MM-DD)") | |
| if end_of_day: | |
| dt = dt.replace(hour=23, minute=59, second=59, microsecond=999999) | |
| return dt | |
| def list_exception_log( | |
| exception_type: str = Query("", alias="type"), | |
| status_code: Optional[int] = Query(None), | |
| from_date: Optional[str] = Query(None, alias="from"), | |
| to_date: Optional[str] = Query(None, alias="to"), | |
| limit: int = Query(100, ge=1, le=1000), | |
| offset: int = Query(0, ge=0), | |
| user: User = Depends(require_module_permission("exception_log", "view")), | |
| db: Session = Depends(get_db), | |
| ): | |
| _require_dda() | |
| q = db.query(ExceptionLog) | |
| if exception_type: | |
| q = q.filter(ExceptionLog.exception_type == exception_type) | |
| if status_code is not None: | |
| q = q.filter(ExceptionLog.status_code == status_code) | |
| start = _parse_date(from_date) | |
| if start: | |
| q = q.filter(ExceptionLog.created_at >= start) | |
| end = _parse_date(to_date, end_of_day=True) | |
| if end: | |
| q = q.filter(ExceptionLog.created_at <= end) | |
| total = q.count() | |
| page_rows = q.order_by(ExceptionLog.created_at.desc()).offset(offset).limit(limit).all() | |
| # Stat cards summarize the *filtered* result set (matching the reference UI's | |
| # "(this page)" labels), computed on the full filtered query, not just page_rows. | |
| filtered = q | |
| errors_500 = filtered.filter(ExceptionLog.status_code >= 500).count() | |
| errors_other = total - errors_500 | |
| unhandled = filtered.filter(ExceptionLog.handled.is_(False)).count() | |
| return { | |
| "logs": [_entry_dict(r) for r in page_rows], | |
| "total": total, | |
| "limit": limit, | |
| "offset": offset, | |
| "stats": {"errors500": errors_500, "errorsOther": errors_other, "unhandled": unhandled}, | |
| } | |
| def exception_log_filters( | |
| user: User = Depends(require_module_permission("exception_log", "view")), | |
| db: Session = Depends(get_db), | |
| ): | |
| _require_dda() | |
| types = [r[0] for r in db.query(ExceptionLog.exception_type).distinct().order_by(ExceptionLog.exception_type) if r[0]] | |
| codes = sorted({r[0] for r in db.query(ExceptionLog.status_code).distinct() if r[0] is not None}) | |
| return {"types": types, "statusCodes": codes} | |
| def get_exception_log_entry( | |
| entry_id: int, | |
| user: User = Depends(require_module_permission("exception_log", "view")), | |
| db: Session = Depends(get_db), | |
| ): | |
| _require_dda() | |
| entry = db.query(ExceptionLog).filter(ExceptionLog.id == entry_id).first() | |
| if not entry: | |
| raise HTTPException(status_code=404, detail="Exception log entry not found") | |
| return _entry_dict(entry, with_traceback=True) | |
| def clear_old_exception_log( | |
| days: int = Query(30, ge=1, le=3650), | |
| user: User = Depends(require_module_permission("exception_log", "delete")), | |
| db: Session = Depends(get_db), | |
| ): | |
| _require_dda() | |
| cutoff = datetime.now(timezone.utc) - timedelta(days=days) | |
| deleted = db.query(ExceptionLog).filter(ExceptionLog.created_at < cutoff).delete(synchronize_session=False) | |
| db.commit() | |
| return {"ok": True, "deleted": deleted} | |