From 661568f2cbd32c3bf0f741ecf212518e490a04a9 Mon Sep 17 00:00:00 2001 From: Pier-Jean Malandrino Date: Fri, 3 Apr 2026 13:50:07 +0200 Subject: [PATCH] Add return type annotations and use 201 for upload All endpoint functions, lifespan context manager, and get_connection now have explicit return types. Upload endpoint returns 201 Created instead of 200 to follow REST conventions. --- document-parser/api/analyses.py | 12 +++++++----- document-parser/api/documents.py | 12 ++++++------ document-parser/main.py | 5 +++-- document-parser/persistence/database.py | 3 ++- document-parser/tests/test_api_endpoints.py | 2 +- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/document-parser/api/analyses.py b/document-parser/api/analyses.py index 8ce3294..8e6112b 100644 --- a/document-parser/api/analyses.py +++ b/document-parser/api/analyses.py @@ -46,7 +46,7 @@ def _to_response(job) -> AnalysisResponse: @router.post("", response_model=AnalysisResponse) -async def create_analysis(body: CreateAnalysisRequest, service: ServiceDep): +async def create_analysis(body: CreateAnalysisRequest, service: ServiceDep) -> AnalysisResponse: """Create a new analysis job for a document.""" if not body.documentId or not body.documentId.strip(): raise HTTPException(status_code=400, detail="documentId is required") @@ -72,14 +72,14 @@ async def create_analysis(body: CreateAnalysisRequest, service: ServiceDep): @router.get("", response_model=list[AnalysisResponse]) -async def list_analyses(service: ServiceDep): +async def list_analyses(service: ServiceDep) -> list[AnalysisResponse]: """List all analysis jobs.""" jobs = await service.find_all() return [_to_response(j) for j in jobs] @router.get("/{job_id}", response_model=AnalysisResponse) -async def get_analysis(job_id: str, service: ServiceDep): +async def get_analysis(job_id: str, service: ServiceDep) -> AnalysisResponse: """Get a single analysis job.""" job = await service.find_by_id(job_id) if not job: @@ -88,7 +88,9 @@ async def get_analysis(job_id: str, service: ServiceDep): @router.post("/{job_id}/rechunk", response_model=list[ChunkResponse]) -async def rechunk_analysis(job_id: str, body: RechunkRequest, service: ServiceDep): +async def rechunk_analysis( + job_id: str, body: RechunkRequest, service: ServiceDep +) -> list[ChunkResponse]: """Re-chunk a completed analysis with new chunking options.""" try: chunks = await service.rechunk(job_id, body.chunkingOptions.model_dump()) @@ -107,7 +109,7 @@ async def rechunk_analysis(job_id: str, body: RechunkRequest, service: ServiceDe @router.delete("/{job_id}", status_code=204) -async def delete_analysis(job_id: str, service: ServiceDep): +async def delete_analysis(job_id: str, service: ServiceDep) -> None: """Delete an analysis job.""" deleted = await service.delete(job_id) if not deleted: diff --git a/document-parser/api/documents.py b/document-parser/api/documents.py index a9b1fd0..88a4d1f 100644 --- a/document-parser/api/documents.py +++ b/document-parser/api/documents.py @@ -25,8 +25,8 @@ def _to_response(doc) -> DocumentResponse: ) -@router.post("/upload", response_model=DocumentResponse) -async def upload(file: UploadFile): +@router.post("/upload", response_model=DocumentResponse, status_code=201) +async def upload(file: UploadFile) -> DocumentResponse: """Upload a PDF document.""" if not file.filename: raise HTTPException(status_code=400, detail="No filename provided") @@ -50,14 +50,14 @@ async def upload(file: UploadFile): @router.get("", response_model=list[DocumentResponse]) -async def list_documents(): +async def list_documents() -> list[DocumentResponse]: """List all documents.""" docs = await document_service.find_all() return [_to_response(d) for d in docs] @router.get("/{doc_id}", response_model=DocumentResponse) -async def get_document(doc_id: str): +async def get_document(doc_id: str) -> DocumentResponse: """Get a single document.""" doc = await document_service.find_by_id(doc_id) if not doc: @@ -66,7 +66,7 @@ async def get_document(doc_id: str): @router.delete("/{doc_id}", status_code=204) -async def delete_document(doc_id: str): +async def delete_document(doc_id: str) -> None: """Delete a document and its file.""" deleted = await document_service.delete(doc_id) if not deleted: @@ -78,7 +78,7 @@ async def preview( doc_id: str, page: int = Query(1, ge=1), dpi: int = Query(150, ge=72, le=300), -): +) -> Response: """Generate a PNG preview of a specific PDF page.""" doc = await document_service.find_by_id(doc_id) if not doc: diff --git a/document-parser/main.py b/document-parser/main.py index 5a9bc57..62de655 100644 --- a/document-parser/main.py +++ b/document-parser/main.py @@ -12,6 +12,7 @@ Conversion engine is selected via CONVERSION_ENGINE env var: from __future__ import annotations import logging +from collections.abc import AsyncIterator from contextlib import asynccontextmanager from fastapi import FastAPI @@ -72,7 +73,7 @@ def _build_analysis_service() -> AnalysisService: @asynccontextmanager -async def lifespan(app: FastAPI): +async def lifespan(app: FastAPI) -> AsyncIterator[None]: await init_db() app.state.analysis_service = _build_analysis_service() logger.info("Docling Studio backend ready (engine=%s)", settings.conversion_engine) @@ -98,7 +99,7 @@ app.include_router(analyses_router) @app.get("/api/health") -def health(): +def health() -> dict[str, str]: """Health check endpoint.""" return { "status": "ok", diff --git a/document-parser/persistence/database.py b/document-parser/persistence/database.py index 87a0856..10d82d3 100644 --- a/document-parser/persistence/database.py +++ b/document-parser/persistence/database.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging import os +from collections.abc import AsyncIterator from contextlib import asynccontextmanager import aiosqlite @@ -82,7 +83,7 @@ async def get_db() -> aiosqlite.Connection: @asynccontextmanager -async def get_connection(): +async def get_connection() -> AsyncIterator[aiosqlite.Connection]: """Context manager that opens and auto-closes a database connection.""" db = await get_db() try: diff --git a/document-parser/tests/test_api_endpoints.py b/document-parser/tests/test_api_endpoints.py index 9826e5c..30b890c 100644 --- a/document-parser/tests/test_api_endpoints.py +++ b/document-parser/tests/test_api_endpoints.py @@ -89,7 +89,7 @@ class TestDocumentEndpoints: "/api/documents/upload", files={"file": ("uploaded.pdf", b"fake-pdf-content", "application/pdf")}, ) - assert resp.status_code == 200 + assert resp.status_code == 201 data = resp.json() assert data["id"] == "new-1" assert data["filename"] == "uploaded.pdf"