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:
@@ -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}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user