Models

Using models for data validation is one of FastAPI's key features, leveraging the power of Pydantic to ensure the integrity and correctness of the data being processed by your API. Here’s a detailed guide on how to use models in FastAPI for data validation:

1. Basic Model Definition with Pydantic

Pydantic models are Python classes that define the schema of your data. Here’s how to create and use a Pydantic model:

Define a Pydantic Model

Create a file models.py (or directly in main.py for simple cases), and define your model:

# models.py
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str = None  # Optional field
    price: float
    tax: float = None  # Optional field
  • BaseModel is the base class provided by Pydantic.
  • Fields are defined as class attributes with their types.
  • Optional fields can have default values (e.g., description: str = None).

Use the Model in Request Body

In your FastAPI routes, you can use the model to validate the request body:

# main.py
from fastapi import FastAPI
from models import Item

app = FastAPI()

@app.post("/items/")
async def create_item(item: Item):
    return {"item": item}
  • FastAPI will automatically parse and validate the incoming JSON data against the Item model.

2. Using Models for Path and Query Parameters

You can also use Pydantic models to validate path and query parameters.

Path Parameters

Define a model for path parameters if they have a complex structure:

# models.py
class User(BaseModel):
    id: int
    name: str

Use it in a route:

# main.py
from models import User

@app.get("/users/{user_id}")
async def read_user(user_id: int):
    return {"user_id": user_id}

Query Parameters

You can pass query parameters directly, but for complex validation, use a Pydantic model:

# main.py
from pydantic import BaseModel
from fastapi import Depends

class QueryParams(BaseModel):
    q: str = None
    limit: int = 10
    offset: int = 0

@app.get("/items/")
async def read_items(params: QueryParams = Depends()):
    return {"query": params.q, "limit": params.limit, "offset": params.offset}
  • Depends() is used to declare dependencies, allowing FastAPI to extract and validate query parameters based on the QueryParams model.

3. Validation and Default Values

Pydantic provides extensive validation and default value capabilities.

Field Validations

You can use Pydantic's validators to enforce constraints:

from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str
    price: float = Field(..., gt=0)  # Price must be greater than 0
    quantity: int = Field(default=1, ge=1)  # Quantity must be at least 1
  • Field allows specifying additional validations and metadata.
  • gt=0 specifies that price must be greater than 0.
  • ge=1 specifies that quantity must be greater than or equal to 1.

Nested Models

You can nest models for complex data structures:

class Manufacturer(BaseModel):
    name: str
    country: str

class Item(BaseModel):
    name: str
    price: float
    manufacturer: Manufacturer
  • The manufacturer field is an instance of the Manufacturer model.

4. Using Models for Response Validation

FastAPI also supports response model validation, ensuring that the response data matches the expected schema.

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item  # The response will be validated against the Item model
  • response_model specifies the model that the response should conform to.

5. Extending and Customizing Validation

Custom Validation

You can define custom validation logic using Pydantic's validators:

from pydantic import BaseModel, validator

class Item(BaseModel):
    name: str
    price: float
    quantity: int

    @validator('name')
    def name_must_be_non_empty(cls, value):
        if not value:
            raise ValueError('Name must not be empty')
        return value

    @validator('price')
    def price_must_be_positive(cls, value):
        if value <= 0:
            raise ValueError('Price must be greater than 0')
        return value
  • Use the @validator decorator to add custom validation logic.

Example Project with Data Validation

Here's a complete example that incorporates these concepts:

Project Structure

fastapi_project/
│
├── main.py
├── models.py
└── requirements.txt

models.py

from pydantic import BaseModel, Field

class Manufacturer(BaseModel):
    name: str
    country: str

class Item(BaseModel):
    name: str
    description: str = None
    price: float = Field(..., gt=0, description="Price must be greater than zero")
    tax: float = None
    manufacturer: Manufacturer

main.py

from fastapi import FastAPI, HTTPException
from models import Item, Manufacturer

app = FastAPI()

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    if item.price <= 0:
        raise HTTPException(status_code=400, detail="Price must be positive")
    return item

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id, "name": "Sample Item"}

@app.get("/manufacturers/", response_model=Manufacturer)
async def read_manufacturer():
    return {"name": "FastAPI Corp", "country": "USA"}

Conclusion

FastAPI's integration with Pydantic provides a powerful and easy-to-use system for data validation. By defining models, you can ensure that the data your API handles is well-structured and adheres to the expected schema, simplifying the development process and reducing the likelihood of errors.