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:
@@ -2,9 +2,8 @@
|
||||
|
||||
import csv
|
||||
import io
|
||||
from urllib.parse import unquote
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Response
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
@@ -17,14 +16,16 @@ from ..constants import (
|
||||
DEFAULT_SORT_DIR,
|
||||
MAX_PAGE_SIZE,
|
||||
)
|
||||
from ..core.nexus import parse_package_path
|
||||
from ..db.engine import get_session
|
||||
from ..db.models import Scan
|
||||
from ..db.queries import build_package_list_query
|
||||
from ..schemas import PackageDetailOut, PackageListResponse
|
||||
|
||||
router = APIRouter(prefix="/api/v1/packages", tags=["packages"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
@router.get("", response_model=PackageListResponse)
|
||||
async def list_packages(
|
||||
limit: int = Query(DEFAULT_PAGE_SIZE, le=MAX_PAGE_SIZE),
|
||||
offset: int = Query(DEFAULT_OFFSET, ge=0),
|
||||
@@ -35,7 +36,7 @@ async def list_packages(
|
||||
sort_by: str = Query(DEFAULT_SORT_BY_PACKAGES),
|
||||
sort_dir: str = Query(DEFAULT_SORT_DIR),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
) -> dict:
|
||||
rows_q, total_q = build_package_list_query(
|
||||
flagged=flagged,
|
||||
ecosystem=ecosystem,
|
||||
@@ -74,7 +75,7 @@ async def export_packages_csv(
|
||||
flagged: bool | None = Query(None),
|
||||
search: str | None = Query(None),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
) -> Response:
|
||||
rows_q, _total_q = build_package_list_query(
|
||||
flagged=flagged,
|
||||
search=search,
|
||||
@@ -118,14 +119,12 @@ async def export_packages_csv(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{name:path}")
|
||||
@router.get("/{name:path}", response_model=PackageDetailOut)
|
||||
async def get_package(
|
||||
name: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
parts = name.rsplit("/", 1)
|
||||
pkg_name = unquote(parts[0])
|
||||
pkg_version = unquote(parts[1]) if len(parts) == 2 else ""
|
||||
) -> dict:
|
||||
pkg_name, pkg_version = parse_package_path(name)
|
||||
|
||||
scans = (
|
||||
(
|
||||
@@ -141,7 +140,7 @@ async def get_package(
|
||||
)
|
||||
|
||||
if not scans:
|
||||
return {"detail": "Not found"}
|
||||
raise HTTPException(status_code=404, detail="Package not found")
|
||||
|
||||
all_findings: list[dict] = []
|
||||
for s in scans:
|
||||
|
||||
Reference in New Issue
Block a user