refactor: FastAPI best practices — return types, Pydantic schemas, middleware, code dedup

- Все 18 роутов получили return type annotations
- Создан schemas.py с Pydantic-моделями (ScanOut, PackageOut, FindingOut, ...)
- API-роуты: response_model на list/detail/export/stats
- 404 через HTTPException(404) вместо {'detail':'Not found'} (200)
- RequestLoggingMiddleware: method, path, status, duration_ms
- Глобальный exception handler: ловит необработанные исключения → 500
- _parse_flagged(): вынесен дублирующийся string→bool
- parse_package_path(): общий для web.py и api_packages.py
- selectinload: вынесены в top-level imports
- harvester: makedirs/mkdtemp/rmtree обёрнуты в asyncio.to_thread()
This commit is contained in:
Marker689
2026-05-10 12:53:33 +03:00
parent 935d96b35a
commit c1258dde99
11 changed files with 188 additions and 55 deletions

View File

@@ -1,10 +1,12 @@
"""GuardDog Nexus — FastAPI application entry point."""
import os
import time
from contextlib import asynccontextmanager
import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
@@ -56,6 +58,21 @@ async def lifespan(app: FastAPI):
log.info("%s shutting down", APP_NAME)
class RequestLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start = time.monotonic()
response = await call_next(request)
duration = (time.monotonic() - start) * 1000
log.info(
"%s %s %s %.1fms",
request.method,
request.url.path,
response.status_code,
duration,
)
return response
app = FastAPI(
title=APP_NAME,
version=APP_VERSION,
@@ -63,6 +80,14 @@ app = FastAPI(
lifespan=lifespan,
)
app.add_middleware(LangMiddleware)
app.add_middleware(RequestLoggingMiddleware)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
log.exception("Unhandled exception on %s %s", request.method, request.url.path)
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
app.include_router(webhook_router)
app.include_router(metrics_router)
@@ -76,7 +101,7 @@ if os.path.isdir(STATIC_DIR):
@app.get("/health")
async def health():
async def health() -> dict:
return {"status": "ok", "version": APP_VERSION}