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
-
TypeError:
Userobject is not iterable
Fix: Ensure you are accessing attributes ofUserinstances correctly. Useuser.nameinstead of iterating over the object. -
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. -
AttributeError: module 'models' has no attribute 'User'
Fix: Verify that yourmodels.pyfile is correctly imported and contains theUserclass 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.