Skip to content

docs(api): add response models for FastAPI OpenAPI documentation - #5261

Open
kunzaatko wants to merge 2 commits into
opendatalab:masterfrom
kunzaatko:feature/fast-api-response-models
Open

docs(api): add response models for FastAPI OpenAPI documentation#5261
kunzaatko wants to merge 2 commits into
opendatalab:masterfrom
kunzaatko:feature/fast-api-response-models

Conversation

@kunzaatko

@kunzaatko kunzaatko commented Jul 13, 2026

Copy link
Copy Markdown

Summary

The FastAPI service in mineru/cli/fast_api.py exposes five HTTP endpoints (POST /file_parse, POST /tasks, GET /tasks/{task_id}, GET /tasks/{task_id}/result, GET /health) whose handlers return manually-built JSONResponse / FileResponse objects. Prior to this change, none of them declared a FastAPI response_model= or responses= parameter, so the OpenAPI schema (/openapi.json and /docs) only advertised a generic 200/202 success body. The actual response shapes - the shared task status payload, the per-file results map, the per-status-code error bodies, and the ZIP-download branches - were undocumented for API consumers.

This change adds Pydantic v2 response models and wires them into the route decorators so that the generated OpenAPI schema accurately reflects every response contract the service actually returns.

Reasoning

  • ** discoverable contract.** Clients (and client generators) reading /openapi.json could not infer what fields a successful /file_parse response contains, what GET /tasks/{id}/result returns when the task is still running (202 vs 200), or which status codes each route can produce. The schema now matches the actual payloads.
  • No runtime impact. The route handlers continue to return JSONResponse / FileResponse directly. FastAPI bypasses response_model validation at runtime when a handler returns a Response subclass, so this is a documentation-only change. The only handler bodies touched were two that previously returned a bare dict (GET /tasks/{id} success and GET /health healthy path) - these are now wrapped in JSONResponse(content=...) so that the docs-only contract holds uniformly (without that wrap, FastAPI would apply response_model validation to bare-dict returns, which would be a behavioural change rather than pure documentation).
  • Forward-compatible. All new models use model_config = ConfigDict(extra="allow"), so additive future keys do not invalidate the documented schema. Nullable task payload fields (started_at, completed_at, error) are modelled as Optional[str] = None, matching what AsyncParseTask.to_status_payload actually emits.

Changes

  • New file mineru/cli/api_response_models.py (~200 lines): ten Pydantic v2 BaseModel classes covering every response shape:
    • TaskStatusPayload (shared base, 10 keys), extended by TaskStatusResponse, TaskSubmissionResponse, TaskMessageResponse, FileParseResultResponse
    • ParseResultInner (the per-file results value; md_content / middle_json / model_output / content_list as Optional[str]; images: dict[str, str])
    • TaskResultResponse (standalone - backend / version / results only, for /tasks/{id}/result 200 JSON)
    • HealthResponse, UnhealthyResponse, HTTPExceptionResponse
  • Edited mineru/cli/fast_api.py:
    • Imports the response models.
    • Each of the five routes now carries response_model= for the success JSON shape and responses= for additional status codes.
    • The two ZIP-capable routes (POST /file_parse, GET /tasks/{id}/result) additionally declare application/zip content via responses={200: {"content": {"application/zip": {}}, ...}} alongside the JSON schema that response_model provides.
    • Per-status error responses (404, 409, 503, 202-not-ready) get a Pydantic model in responses=.
    • GET /tasks/{id} 200 path and GET /health healthy path wrapped in JSONResponse(content=...) (see Reasoning).

Verification

  • from mineru.cli.fast_api import app; app.openapi() renders with all new schemas present and per-route response codes matching the live handler inventory:
    • POST /file_parse → 200, 409, 422, 503
    • POST /tasks → 202, 422
    • GET /tasks/{id} → 200, 404, 422
    • GET /tasks/{id}/result → 200, 202, 404, 409, 422
    • GET /health → 200, 503
  • Dual media type on ZIP-capable routes confirmed: 200 exposes both application/json (from response_model) and application/zip (from responses override).
  • ASGI smoke test via fastapi.testclient.TestClient: GET /health returns the expected 11-key JSON body; GET /tasks/{unknown_id} still returns FastAPI\u2019s default {"detail": "Task not found"} 404. Runtime behaviour preserved.

Design notes

  • HTTPExceptionResponse (with detail: str) is defined locally rather than imported because FastAPI does not ship a reusable model for the {"detail": ...} shape. The only error schema FastAPI auto-publishes is HTTPValidationError (422), defined as a hardcoded JSON-Schema dict inside fastapi/openapi/utils.py, not an importable BaseModel. Defining our own tiny error model is the idiomatic community pattern (examples: Donkie/Spoolman Message, several ErrorResponse models in ecosystem projects).
  • Per-route responses= is used rather than router-level responses because this file uses @app.get/@app.post(...) decorators directly rather than APIRouter. If the file is ever migrated to APIRouter, the shared error models could be hoisted to a single APIRouter(responses=common_error_responses) declaration.

Scope note

I would be happy to expand this beyond the current docs-only scope into a deeper refactor of the FastAPI application as a properly modelled interface - i.e. replacing the manually-built JSONResponse(content=dict) / FileResponse returns with Pydantic response objects (or plain dicts returned through a typed response_model) so that FastAPI enforces the contract at runtime, not just in the schema. That would catch schema drift between the handlers and the documented response models, and would let the request and response types together form a single typed API surface. I kept this PR strictly documentation-only to avoid changing runtime behaviour without discussion, but the modelled-interface refactor is a natural follow-up if it is desirable here.

Implementation

This change was implemented with the assistance of an LLM coding agent. All generated code was read in full and reviewed for correctness by a human before being pushed.

Introduces `mineru/cli/api_response_models.py` with nine Pydantic models
that describe every JSON response shape across the FastAPI endpoints.
Models are wired into routes via `response_model=` and `responses=`
parameters so that `/openapi.json` and `/docs` accurately reflect
success, error, and ZIP-download contracts. Handlers continue to return
JSONResponse/FileResponse directly, so this change is documentation-only
and does not affect runtime behaviour.
Wraps the healthy-branch dict in `JSONResponse(content=...)` so that
FastAPI bypasses `response_model` validation, matching the behaviour of
the unhealthy branch and keeping the docs-only response-model contract
intact.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. documentation Improvements or additions to documentation labels Jul 13, 2026
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@kunzaatko

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant