How to Use Pydantic for Data Validation in FastAPI

FastAPI is a modern, fast (high-performance) web framework for building APIs with Python 3.7+ based on standard Python type hints. One of the key features that makes FastAPI so powerful is its seamless integration with Pydantic, a data validation and settings management library using Python type annotations. Pydantic enforces type hints at runtime and provides user-friendly errors when data doesn’t match the expected schema. In this blog, we’ll explore how to use Pydantic for data validation in FastAPI, covering fundamental concepts, usage methods, common practices, and best practices.

Table of Contents

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts

Pydantic Models

Pydantic models are the core of data validation in Pydantic. They are defined as Python classes that inherit from pydantic.BaseModel. Each attribute of the class represents a field in the data schema, and the type hint specifies the expected data type.

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None

In this example, Item is a Pydantic model with three fields: name (a string), price (a float), and is_offer (a boolean with a default value of None).

Data Validation

When an instance of a Pydantic model is created, Pydantic automatically validates the input data against the defined schema. If the data doesn’t match the expected type, Pydantic raises a ValidationError.

try:
    item = Item(name="Foo", price="bar")
except ValueError as e:
    print(e)

In this case, a ValidationError will be raised because the value "bar" cannot be converted to a float.

FastAPI Integration

FastAPI uses Pydantic models to validate the input data of API requests and to serialize the output data of API responses. When a Pydantic model is used as a parameter in a FastAPI route function, FastAPI automatically validates the incoming data against the model’s schema.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None

@app.post("/items/")
async def create_item(item: Item):
    return item

In this example, the create_item route expects a JSON object in the request body that matches the Item model’s schema. If the data is invalid, FastAPI will return a 422 Unprocessable Entity response with detailed error messages.

Usage Methods

Defining Pydantic Models

To define a Pydantic model, create a Python class that inherits from pydantic.BaseModel and define the fields with type hints. You can also use default values, optional fields, and more complex types such as lists, dictionaries, and nested models.

from pydantic import BaseModel
from typing import List

class Item(BaseModel):
    name: str
    price: float
    tags: List[str] = []
    description: str = None

Using Pydantic Models in FastAPI Routes

To use a Pydantic model in a FastAPI route, simply use it as a parameter type in the route function. FastAPI will automatically validate the incoming data against the model’s schema.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

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

@app.post("/items/")
async def create_item(item: Item):
    return {"name": item.name, "price": item.price}

Handling Validation Errors

FastAPI automatically handles ValidationError exceptions and returns a 422 Unprocessable Entity response with detailed error messages. You can also customize the error handling by defining a custom exception handler.

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import ValidationError

app = FastAPI()

@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
    return JSONResponse(
        status_code=422,
        content={"detail": exc.errors()}
    )

Common Practices

Using Optional Fields

Use optional fields when a field is not required in the data schema. You can mark a field as optional by using the typing.Optional type hint or by providing a default value.

from pydantic import BaseModel
from typing import Optional

class Item(BaseModel):
    name: str
    price: float
    description: Optional[str] = None

Using Lists and Dictionaries

Pydantic supports lists and dictionaries as field types. You can specify the type of the elements in the list or the keys and values in the dictionary using type hints.

from pydantic import BaseModel
from typing import List, Dict

class Item(BaseModel):
    name: str
    price: float
    tags: List[str] = []
    attributes: Dict[str, str] = {}

Using Nested Models

You can use nested models to represent more complex data structures. Simply define a new Pydantic model and use it as a field type in another model.

from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    country: str

class User(BaseModel):
    name: str
    age: int
    address: Address

Best Practices

Use Descriptive Field Names

Use descriptive field names in your Pydantic models to make the data schema more understandable. Avoid using abbreviations or cryptic names.

Validate Input Early

Validate the input data as early as possible in the application to prevent invalid data from propagating through the system. FastAPI’s automatic validation at the route level is a great way to achieve this.

Document the Data Schema

Document the data schema of your Pydantic models using docstrings or comments. This will help other developers understand the expected data format and the purpose of each field.

from pydantic import BaseModel

class Item(BaseModel):
    """
    Represents an item in the inventory.

    Attributes:
        name (str): The name of the item.
        price (float): The price of the item.
        description (str, optional): A description of the item.
    """
    name: str
    price: float
    description: str = None

Conclusion

Pydantic is a powerful tool for data validation in FastAPI. By using Pydantic models, you can easily define and enforce data schemas, validate input data, and serialize output data. FastAPI’s seamless integration with Pydantic makes it easy to build robust and reliable APIs with minimal code. By following the common practices and best practices outlined in this blog, you can ensure that your data validation is efficient, effective, and easy to maintain.

References