# Request and Response Models With Pydantic — Django / FastAPI

Source: https://www.skillbyai.com/en/django-fastapi/f-models

> Validate in, filter out.

## BaseModel, Field, response_model

Request bodies are **Pydantic models**: fields with types and constraints (`Field(min_length=2)`, `gt=0`) are validated before your code runs, and invalid input returns 422 with every problem listed. A **response_model** (or return type annotation) filters and serialises the output, so internal fields such as costs or password hashes never leak. Set `status_code=201` for creation endpoints. Keep separate models for input and output.

## Models, dependencies, async, docs

Pydantic models validate data, dependencies share logic, async handles I/O concurrency, and OpenAPI documents it all.

![Four ideas: Pydantic models, dependencies, async, OpenAPI.](assets/figures/django-fastapi/section-2-map.svg) — Figure 2.1 — Models, dependencies, async and docs.

## Validation and output filtering, run

I ran this with Python 3.12, FastAPI 0.142.2, Pydantic 2.13 and Starlette 1.7, calling the app through FastAPI's TestClient (no server needed). A valid product returns 201 and the internal_cost field is stripped by the response model. An invalid body returns 422 listing both the too-short name and the non-positive price.

```python
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field

class ProductIn(BaseModel):
    name: str = Field(min_length=2, max_length=50)
    price: float = Field(gt=0)
    tags: list[str] = []

class ProductOut(BaseModel):
    id: int
    name: str
    price: float

app = FastAPI()
DB: dict[int, dict] = {}

@app.post("/products", response_model=ProductOut, status_code=201)
def create(p: ProductIn):
    pid = len(DB) + 1
    DB[pid] = {"id": pid, **p.model_dump(), "internal_cost": 42}   # extra field
    return DB[pid]                       # response_model filters the output

client = TestClient(app)
r = client.post("/products", json={"name": "Pen", "price": 899, "tags": ["office"]})
print(r.status_code, r.json())
r = client.post("/products", json={"name": "X", "price": -5})
print(r.status_code, [(e["loc"][-1], e["msg"]) for e in r.json()["detail"]])
```

Output:

```
201 {'id': 1, 'name': 'Pen', 'price': 899.0}
422 [('name', 'String should have at least 2 characters'), ('price', 'Input should be greater than 0')]
```

## Separate input and output models

ProductIn, ProductOut and ProductDB models make it obvious what clients can send and what they receive.

**Quiz:** What does response_model do with fields not in the model?

- [ ] Adds them as strings
- [x] Removes them from the response
- [ ] Raises an error
- [ ] Encrypts them

*Answer:* Removes them from the response. Output filtering prevents leaks.
