422 Unprocessable Entity in FastAPI request validation layer
0 reputation · 01 Jun 2025, 11:07 UTC
0 reputation · 01 Jun 2025, 11:07 UTC
The goal is to clarify where request validation cost sits in FastAPI and how validation error responses can be controlled without changing the default 422 Unprocessable Entity behavior.
Validation is performed by the request parsing layer before the path operation function is invoked. Errors are raised by the framework rather than user code and are not visible in endpoint profiling unless the whole ASGI call is wrapped. FastAPI does not expose a first-party setting to change the validation error aggregation strategy or to map specific validation failures to different HTTP status codes. The behavior is inherited from Pydantic and can be altered only via custom exception handlers or model configuration.
There is uncertainty about measuring per-segment timing for validation versus dependency resolution and business logic, and about the supported limits for fine-grained error control.
Is there a documented way to distinguish validation time from dependency resolution time in the ASGI call? Can specific Pydantic validation failures be mapped to different HTTP status codes without a custom exception handler? What is the supported approach for fine-grained error control?
34775 reputation · 01 Jun 2025, 13:44 UTC
FastAPI does not expose a documented first-party way to separate request-validation time from dependency-resolution time; both happen in the same pre-endpoint solve phase. It also does not support mapping individual Pydantic validation failures to different HTTP status codes without a custom exception handler. The supported approach for fine-grained control is to register a handler for fastapi.exceptions.RequestValidationError and inspect exc.errors().
Validation is performed by the request parsing layer before the path operation function is invoked. Errors are raised by the framework rather than user code and are not visible in endpoint profiling unless the whole ASGI call is wrapped. FastAPI performs body parsing, schema validation, and dependency resolution together before calling your endpoint. This behavior is well established, but the exact internal boundaries are private and version-sensitive; verify against your installed FastAPI and Pydantic versions before relying on them.
To attribute cost, wrap the ASGI application or override APIRoute.get_route_handler(). A minimal ASGI timing middleware can record total request time and endpoint time, leaving the difference as pre-endpoint work. Finer attribution between validation and dependencies requires custom instrumentation.
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def timing(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
response.headers["X-Total-Time"] = f"{duration:.6f}"
return response
This measures the whole ASGI call, including ASGI overhead. It does not isolate a specific dependency without extra spans or counters.
FastAPI’s default handler returns 422 Unprocessable Entity with a detail list. To change status codes per error, add an exception handler. Pydantic field constraints, Annotated metadata, and model config can change validation semantics, but they do not select HTTP status codes.
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def handler(request: Request, exc: RequestValidationError):
errors = exc.errors()
# Pydantic v2 shape: type, loc, msg, ctx
if any(e.get("type") == "value_error" for e in errors):
return JSONResponse(status_code=400, content={"detail": errors})
return JSONResponse(status_code=422, content={"detail": errors})
Do not raise HTTPException from Pydantic validators to control status; raise ValueError, AssertionError, or PydanticCustomError and map statuses in the handler. Pydantic v1 and v2 error shapes differ, so handler code should tolerate both if you support multiple versions.
RequestValidationError handler that returns 400 for one error type and 422 otherwise. Verify with TestClient/httpx that only that error type changes status.fastapi.__version__ and pydantic.VERSION, then inspect fastapi.dependencies.utils.solve_dependencies in your installed version before relying on internals.If exact instrumentation points matter for your profiling setup, confirm which FastAPI and Pydantic major versions you are running; the handler shape and internal boundaries differ.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.