Build With Abdallah logo Build With Abdallah Software · AI · Automation
Tutorial 7 min read Jun 07, 2026

Getting Started with Pydantic for Data Validation in Python 2026

Pydantic is a data validation and settings management library for Python, known for its ease of use and efficiency. With Python's ongoing evolution, data validation has become more

A
Abdallah Mohamed
Senior Full-Stack Engineer
Getting Started with Pydantic for Data Validation in Python 2026

Getting Started with Pydantic for Data Validation in Python 2026

Pydantic is a data validation and settings management library for Python, known for its ease of use and efficiency. With Python's ongoing evolution, data validation has become more critical to ensure that applications handle data reliably and predictably. Pydantic offers a straightforward approach to defining and validating data structures, making it a valuable tool for modern Python applications.

Prerequisites

To follow this tutorial, ensure you have Python 3.8 or later installed on your system. You will also need to install Pydantic. Use the following commands to set up your environment:

# Install Python 3.8 or later if not already installed
# For Ubuntu
sudo apt-get update
sudo apt-get install python3.8 python3.8-venv python3-pip

# For macOS using Homebrew
brew update
brew install python@3.8

# Create a virtual environment
python3.8 -m venv pydantic-tutorial

# Activate the virtual environment
# On Windows
pydantic-tutorial\Scripts\activate

# On macOS and Linux
source pydantic-tutorial/bin/activate

# Install Pydantic
pip install pydantic

Project Structure

Before diving into the code, let's set up a basic project structure:

pydantic_tutorial/
    ├── main.py
    └── models.py

Step 1: Basic Data Model

We'll start by creating a basic data model using Pydantic. This will introduce you to how Pydantic models are defined and used.

Create a file named models.py and add the following code:

# models.py
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

In this example, we define a User model with three fields: id, name, and email. Each field is annotated with a type, and Pydantic uses these type annotations to validate the data.

Step 2: Validating Data

Now that we have a basic model, let's see how Pydantic validates data. Create a file named main.py and add the following code:

# main.py
from models import User

def main():
    # Valid data
    user = User(id=1, name='John Doe', email='john.doe@example.com')
    print(user)

    # Invalid data
    try:
        user = User(id='one', name='John Doe', email='john.doe@example.com')
    except ValueError as e:
        print(f"Validation Error: {e}")

if __name__ == '__main__':
    main()

In this script, we create a User instance with valid data and print it. Then, we attempt to create another User instance with an invalid id type, which raises a ValueError. Pydantic automatically validates the data types and raises errors for any mismatches.

Step 3: Adding Field Constraints

Pydantic allows you to add constraints to fields to enforce more specific validation rules. Let's update our User model to include some constraints.

Modify models.py as follows:

# models.py
from pydantic import BaseModel, EmailStr, constr

class User(BaseModel):
    id: int
    name: constr(min_length=2, max_length=50)
    email: EmailStr

In this updated model, we use constr to specify that the name field must be between 2 and 50 characters long. We also use EmailStr, a Pydantic-provided type, to ensure that the email field contains a valid email address.

Update main.py to test these new constraints:

# main.py
from models import User

def main():
    # Valid data
    user = User(id=1, name='John Doe', email='john.doe@example.com')
    print(user)

    # Invalid data
    try:
        user = User(id=1, name='J', email='john.doe@example.com')
    except ValueError as e:
        print(f"Validation Error: {e}")

    try:
        user = User(id=1, name='John Doe', email='john.doe.com')
    except ValueError as e:
        print(f"Validation Error: {e}")

if __name__ == '__main__':
    main()

This code demonstrates how Pydantic enforces the constraints on the name and email fields. The first invalid data example will raise an error because the name is too short, and the second because the email is not valid.

With these steps, you have learned how to define a basic Pydantic model, validate data, and add field constraints. In the next part, we will explore more advanced features of Pydantic, including nested models and custom validators.

Step 4: Nested Models

Pydantic supports nested models, allowing you to represent more complex data structures. Let's create a nested model by adding an Address model to our project.

First, update models.py to include the Address model:

# models.py
from pydantic import BaseModel, EmailStr, constr

class Address(BaseModel):
    street: str
    city: str
    zip_code: constr(min_length=5, max_length=10)

class User(BaseModel):
    id: int
    name: constr(min_length=2, max_length=50)
    email: EmailStr
    address: Address

In this code, the User model now includes an address field, which is an instance of the Address model. This setup allows you to validate complex, hierarchical data structures.

Update main.py to test the nested models:

# main.py
from models import User, Address

def main():
    # Valid data
    address = Address(street='123 Main St', city='Anytown', zip_code='12345')
    user = User(id=1, name='John Doe', email='john.doe@example.com', address=address)
    print(user)

    # Invalid data
    try:
        address = Address(street='123 Main St', city='Anytown', zip_code='123')
    except ValueError as e:
        print(f"Validation Error: {e}")

    try:
        user = User(id=1, name='John Doe', email='john.doe@example.com', address={'street': '123 Main St', 'city': 'Anytown', 'zip_code': '12345'})
    except ValueError as e:
        print(f"Validation Error: {e}")

if __name__ == '__main__':
    main()

This script demonstrates creating a valid User with a nested Address. It also shows how Pydantic raises errors for invalid nested data.

Step 5: Custom Validators

Pydantic allows you to define custom validators for more complex validation logic. Let's add a custom validator to ensure that the zip_code in the Address model contains only digits.

Modify models.py to include the custom validator:

# models.py
from pydantic import BaseModel, EmailStr, constr, validator

class Address(BaseModel):
    street: str
    city: str
    zip_code: constr(min_length=5, max_length=10)

    @validator('zip_code')
    def zip_code_must_be_numeric(cls, value):
        if not value.isdigit():
            raise ValueError('zip_code must contain only digits')
        return value

class User(BaseModel):
    id: int
    name: constr(min_length=2, max_length=50)
    email: EmailStr
    address: Address

This code adds a custom validator to the Address model, ensuring that the zip_code is numeric.

Update main.py to test the custom validator:

# main.py
from models import User, Address

def main():
    # Valid data
    address = Address(street='123 Main St', city='Anytown', zip_code='12345')
    user = User(id=1, name='John Doe', email='john.doe@example.com', address=address)
    print(user)

    # Invalid data
    try:
        address = Address(street='123 Main St', city='Anytown', zip_code='12a45')
    except ValueError as e:
        print(f"Validation Error: {e}")

if __name__ == '__main__':
    main()

This example shows how the custom validator catches non-numeric zip_code entries.

Complete Working Example

Here's the complete code for both models.py and main.py:

# models.py
from pydantic import BaseModel, EmailStr, constr, validator

class Address(BaseModel):
    street: str
    city: str
    zip_code: constr(min_length=5, max_length=10)

    @validator('zip_code')
    def zip_code_must_be_numeric(cls, value):
        if not value.isdigit():
            raise ValueError('zip_code must contain only digits')
        return value

class User(BaseModel):
    id: int
    name: constr(min_length=2, max_length=50)
    email: EmailStr
    address: Address
# main.py
from models import User, Address

def main():
    # Valid data
    address = Address(street='123 Main St', city='Anytown', zip_code='12345')
    user = User(id=1, name='John Doe', email='john.doe@example.com', address=address)
    print(user)

    # Invalid data
    try:
        address = Address(street='123 Main St', city='Anytown', zip_code='12a45')
    except ValueError as e:
        print(f"Validation Error: {e}")

if __name__ == '__main__':
    main()

Common Errors and Fixes

  1. TypeError: User object is not iterable
    Fix: Ensure you are accessing attributes of User instances correctly. Use user.name instead of iterating over the object.

  2. ValidationError: 1 validation error for User
    Fix: Check the data types and constraints in your model. Ensure the data being passed matches the expected types and constraints.

  3. AttributeError: module 'models' has no attribute 'User'
    Fix: Verify that your models.py file is correctly imported and contains the User class definition.

Conclusion

In this tutorial, we explored Pydantic for data validation in Python. We covered creating basic and nested models, adding field constraints, and implementing custom validators. Pydantic provides a powerful way to ensure data integrity in Python applications, making it an essential tool for developers.

Sources