top of page

Data Validation with Pydantic - FastAPI Tutorial


Introduction to Pydantic:

FastAPI, a modern, fast web framework for building APIs with Python 3.7+ based on standard Python type hints, leverages Pydantic for data validation. Pydantic, a data validation and parsing library, plays a crucial role in ensuring that the data your API receives and responds with is accurate, consistent, and adheres to specified data models.


Role of Pydantic in FastAPI:

Pydantic seamlessly integrates with FastAPI to bring robust data validation to your API endpoints. It allows you to define data models using Python's type hints, making the process of validating and parsing data both intuitive and efficient. FastAPI utilizes Pydantic models not only for validation but also for automatic OpenAPI documentation generation, improving both development speed and API documentation accuracy.


Why should you use Pydantic?

Pydantic is a data validation and parsing library for Python that provides several advantages, making it a popular choice in various scenarios. Here are some reasons why you might want to use Pydantic:

  1. Data Validation: Pydantic allows you to define data models using Python type hints, and it automatically validates input data against these models. This helps ensure that the data adheres to the expected structure and types, reducing the chances of runtime errors.

  2. Automatic Data Conversion: Pydantic can automatically convert and parse input data into Python objects based on the defined models. This simplifies the process of handling data coming from different sources, such as JSON, form data, or API requests.

  3. Type Hinting: Pydantic leverages Python's type hinting system, which enhances code readability and provides static analysis tools with information about the expected types of variables. This can lead to better code quality and improved development workflows.

  4. Schema Documentation: Pydantic can generate detailed documentation for your data models, making it easier for developers to understand the expected structure of the data. This is especially useful when working in teams or when documenting APIs.

  5. Serialization and Deserialization: Pydantic facilitates the serialization (converting Python objects to data formats like JSON) and deserialization (converting data formats back to Python objects) processes. This is crucial when working with APIs or storing data in different formats.

  6. Configurable Validation: Pydantic allows you to customize and configure the validation process, enabling you to define custom validation functions, error messages, and more. This flexibility is useful in handling specific validation requirements.

  7. Integration with FastAPI: Pydantic is often used in conjunction with FastAPI, a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. FastAPI leverages Pydantic for data validation and automatic OpenAPI documentation generation.


Defining Pydantic Models for Data Validation:

Let's see into the process of defining Pydantic models for data validation in the context of FastAPI.


Installation:

Before getting started, ensure that Pydantic is installed in your project. If not, install it using:


pip install pydantic

Creating a Pydantic Model:


from pydantic import BaseModel

Define a Pydantic model by creating a class that inherits from BaseModel. Add attributes with their corresponding types to the class.


# Pydantic model for the Student
class Student(BaseModel):
    name: str
    age: int
    grade: str

In this example, we've defined an Student model with attributes like name (mandatory str), description (optional str with a default value of None), age (mandatory int), and grade(optional str with a default value of None).



Using the Pydantic Model in FastAPI:

Now, integrate the Pydantic model into a FastAPI endpoint for data validation.



from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

# Create an instance of FastAPI
app = FastAPI()

# Define a simple in-memory database for student information
db = []

# Pydantic model for the Student
class Student(BaseModel):
    name: str
    age: int
    grade: str

@app.post("/students/")
def add_student(new_student: Student):
    student = Student(name=new_student.name, age=new_student.age, grade=new_student.grade)
    db.append(student)
    return {"message": "Student added successfully"}

# Route to get all students
@app.get("/students/", response_model=List[Student])
def get_all_students():
    return db

In the example above, the add_student endpoint takes an new_student parameter of type Student (Pydantic model). FastAPI automatically validates incoming data against the specified model, ensuring it adheres to the defined structure and data types. If the data is valid, the endpoint processes the request, and you can access the validated data through the new_student parameter.


Output :

Add Student information


Display Student information


Note : If you dont know how to add student information read our previous blog : - Click Here


Additional Considerations for Pydantic Models:

To make your understanding of Pydantic models even more comprehensive, let's see into some additional considerations and advanced features.


1.Model Configurations:

Pydantic allows the customization of model behavior through the Config class. Consider a scenario where you want to allow population of model fields using field names, providing flexibility when receiving data. You can achieve this through the Config class within the Pydantic model: With this configuration, when receiving data from an external source, it becomes possible to populate model fields using field names directly.




from pydantic import BaseModel

class Student(BaseModel):
    name: str
    age: int
    grade: str

    class Config:
        allow_population_by_field_name = True

# Create an instance of Student and populate it using field names
student_data = {
    "name": "rahul",
    "age": 20,
    "grade": "A"
}

student_instance = Student(**student_data)

# Accessing attributes
print("Name:", student_instance.name)
print("Age:", student_instance.age)
print("Grade:", student_instance.grade)



In this above example, we create a dictionary student_data with field names as keys and corresponding values. Then, we use the ** unpacking operator to pass the dictionary items as keyword arguments to create an instance of the Student model. Finally, we print out the attributes of the created instance.


Output :


2. Validation Rules and Custom Validators:

Pydantic models support a variety of built-in validation rules. Additionally, you can define custom validation functions within the model to enforce specific business logic.


Extend the Student model to include a custom validator for the age attribute, ensuring that the age is within a reasonable range:




from pydantic import BaseModel, ValidationError, validator

class Student(BaseModel):
    name: str
    age: int
    grade: str

    @validator("age")
    def validate_age(cls, value):
        if not 10 <= value <= 18:
            raise ValueError("Age must be between 10 and 18")
        return value

# Example usage
try:
    # Valid student data
    valid_student_data = {"name": "ajay", "age": 15, "grade": "A"}
    valid_student = Student(**valid_student_data)
    print("Valid Student Data:", valid_student)

    # Invalid student data (age less than 10)
    invalid_student_data = {"name": "rohit", "age": 8, "grade": "B"}
    invalid_student = Student(**invalid_student_data)  # This line will raise a ValidationError
    print("Invalid Student Data:", invalid_student)

except ValidationError as e:
    print("Validation Error:", e)


In the above example, we create instances of the Student class with both valid and invalid data. The ValidationError exception will be raised if the data does not meet the validation criteria specified in the custom validator.


Output:


3. Reusable Components with Pydantic:

Imagine you want to extend the Student model to include additional contact information. Instead of duplicating similar structures, you can create a separate Pydantic model for contact details and reuse it within the Student model:




from pydantic import BaseModel

class ContactDetails(BaseModel):
    email: str
    phone: str

class Student(BaseModel):
    name: str
    age: int
    grade: str
    contact_details: ContactDetails

# Creating an instance of the ContactDetails model
contact_info = ContactDetails(email="ajay@gmail.com", phone="78461584")

# Creating an instance of the Student model with contact details
student_info = Student(name="ajay", age=20, grade="A", contact_details=contact_info)

# Accessing and printing attributes of the Student instance
print("Student Name:", student_info.name)
print("Student Age:", student_info.age)
print("Student Grade:", student_info.grade)
print("Contact Email:", student_info.contact_details.email)
print("Contact Phone:", student_info.contact_details.phone)



In the above example, we create an instance of the ContactDetails model with specific email and phone values. Then, we create a Student instance, providing values for the student's name, age, grade, and the previously created ContactDetails instance as the contact_details. Finally, we print out various attributes of the Student instance.


Output :



we've explored the basics of using Pydantic for data validation, especially when working with FastAPI to build Python APIs. We've talked about why Pydantic is useful, like making sure your data is correct and easily handling different data sources. The blog walks you through creating Pydantic models step by step, which help define how your data should look. We've also touched on some cool features like customizing how Pydantic works and adding special rules to check your data. I hope, this blog is a helpful for anyone getting started with Pydantic and FastAPI, making data validation in Python a bit less daunting.


If you require assistance with the implementation of Fast API, or if you need help with related projects, please don't hesitate to reach out to us.

bottom of page