Field(), Query(), and Path() validators all fit together, and when to reach for the Annotated style.
The Three-Model Pattern
A real application keeps incoming data, internal business data, and outgoing response data strictly separated:Request Processing Flow
Every request passes through multiple validation stages before reaching your business logic:1. Path Parameter Validation
Path parameters identify a specific resource. AddPath() constraints using Annotated:
- The value can be converted to
int - It is greater than zero (
gt=0)
/employees/abc or /employees/-5, FastAPI returns a 422 error automatically — your function never runs.
2. Query Parameter Validation
Query parameters filter, search, sort, or paginate results:3. Request Body Validation
When clients create or update resources, they send JSON in the request body:EmployeeCreate instance, and passes it to your function. If validation fails, 422 is returned immediately.
4. Why Isn’t the Request Model Enough?
The request model represents only what the client is allowed to send. But your application usually needs to generate additional data automatically — data that should never come from the client:- Employee ID
- Employee Code
- Tax ID
- Joining Date
- Created Timestamp
5. Internal Model
After validation, your application creates its own complete working object:id, employee_code, tax_id, or joined_at — these are generated by your application logic.
6. Response Model
The application should not expose its internal model directly. Create a Response Model containing only the fields that clients should receive:EmployeeInternal contains salary, employee_code, tax_id, and joined_at, the client only receives:
Validation Tools Reference
Field() — Request Body Validation
Query() — Query Parameter Validation
Path() — Path Parameter Validation
Common Validation Options
Annotated — Recommended Style
In Pydantic v2, the recommended way to write validation is with Annotated:
age: int = Field(gt=0, lt=100)) still works, but Annotated is the preferred style going forward.
model_config and from_attributes
When your API needs to return data from a database ORM (like SQLAlchemy), Pydantic needs to read attribute values from an object rather than a dictionary. Configure this with:
from_attributes=True, Pydantic reads values using dictionary keys (data["name"]). With it, Pydantic reads from object attributes (data.name) — which is how ORM objects work.
Use model_validate() to convert an ORM object to your response model:
Best Practice: Use separate models for Request, Internal, and Response. Each has one responsibility — validation, processing, and safe exposure respectively. This keeps your application secure, flexible, and easy to maintain.