Mastering Pydantic in Python: A Complete Beginner’s Guide

What is Pydantic ?
Pydantic is a Python library that helps us in defining and validating data models easily. Whenever we are handling data or user queries while developing, it is important to make sure that data is valid and consistent. If we do not do proper validation we might face errors while developing applications.
Example:
A food delivery company might receive order data as JSON, and Pydantic validates that all required fields (order_id, items, amount, etc.) have correct types.
Companies using FastAPI, one of the most popular modern Python web frameworks rely on Pydantic for defining request and response models that instantly validate incoming JSON payloads before reaching business logic. Beyond web services, companies use Pydantic in data-processing pipelines, ETL workflows, and microservices to ensure that data contracts between services remain stable and errors surface early.
Installation
pip install pydantic or uv add pydantic
We would also need to install fastapi, uvicorn, dotenv and pydantic-settings(used for .env).
Basics
BaseModel
Pydantic is a Python library that helps us in defining and validating data models easily. So what do I mean by “data models“, let’s understand with an example. Suppose there is user data which we may be receiving or sending through API having JSON data.
{
"name": "Arpit",
"age": "20",
"email": "arpit@example.com",
"is_active": "True"
}
Here we to ensure that :
Name is String
age is integer
email is a valid email address
For validation of this data we can define “data model” using a BaseModel
from pydantic import BaseModel, EmailStr
class User(BaseModel):
name: str
age: int
email: EmailStr
is_active: bool
Let’s give some input to data model and how validation happens here.
input_data = { "name": "arpit", "age": 19, "email": "abc@gmail.com", "is_active": "True" }
print(User(**input_data))
Did you find any issue in input_data? In is_active field True is kept as String. Surprisingly, pydantic will not show any error to this and in output it converts it.
#OUTPUT
name="arpit" age=19, email="abc@gmail.com", is_active=True
Pydantic automatically change it into a proper boolean (True). That’s one of Pydantic’s superpowers, it validates and parses the data for you. This feature is very helpful when you receive data from JSON payloads, web forms, or other systems where types might not match perfectly. Pydantic will do its best to make your data usable without throwing errors - unless the data cannot be reasonably converted.
Wherever Pydantic is able to change it do without any errors.
Let’s do a assignment: Create Product model with id, name, price, in_stock
from pydantic import BaseModel
class Product(BaseModel):
id: int
name: str
price: float
in_stock: bool = True #default value
Fields
In pydantic,
Every class that inherits BaseModel is a Data Model.
Every attribute of that class like user_id, name, price, etc. are Fields.
from pydantic import BaseModel
from typing import List, Dict, Optional
class Cart(BaseModel):
user_id: int # Fields
items: List[str] # Fields
quantities: Dict[str, int] # Fields
class BlogPost(BaseModel):
title: str
content: str
image_url: Optional[str] = None
If you see the above examples I have imported a module named typing. The typing module in Python provides type hints. Pydantic looks at these hints and enforces them at runtime.
Key types used in above examples:
List[str] - list containing strings.
Dict[str, int] - dictionary where keys are strings and values are integers.
Optional[str] - a string or None.
Let’s solve a simple question. Create Employee model with Fields id, name(min 3 chars), department and salary(must be >=10,000).
So, how do we get capabilities to bring field-specific validations (min 3 char and must be >=). Pydantic provides special types and the Field( ) helper.
from pydantic import BaseModel, Field
from typing import Optional
class employee(BaseModel):
id: int
name: str = Field(
..., # ... - three dots represents required field
min_length=3,
max_length=50,
description="Employee Name",
example="Arpit"
)
department: Optional[str] = General
salary: float = Field(..., ge=10000)
Adding Validators and Computed Fields to Pydantic Models
Pydantic provides a powerful set of tools to make sure your data matches the business rules you need. A field validator is a method that runs every time whenever a particular field is set. A field validator is a special method that checks a field’s value whenever it’s set.
With @field_validator, you can add your own rules. For example, making sure a username has enough characters.
from pydantic import BaseModel, field_validator
class User(BaseModel):
username: str
@field_validator('username')
def username_length(cls, v):
if len(v) < 4:
raise ValueError("Username must have at least 4 chars.")
return v
A model_validator is a special method that checks or updates the whole model after all its fields have been validated.
mode=’before’ - The validator runs first, before any of the fields are validated.
mode=’after’ - The validator runs last, after all the fields have been successfully validated.
from pydantic import BaseModel, model_validator
class SignUpData(BaseModel):
password: str
confirm_password: str
@model_validator(mode='after')
def password_match(cls, values):
if values.passowrd != values.confirm_password:
raise ValueError("Password did not matched.")
return values
Computed fields are values that Pydantic calculates automatically based on other fields in the model. You don’t pass them as input, they’re generated when the model is created. They help you add useful, read-only properties to your model without extra work. Read-only means you cannot set or change the value yourself. It is automatically generated by the model and is only available when you look at the model’s data.
# Example 1
from pydantic import BaseModel, computed_field
class User(BaseModel):
name: str
age: int
@computed_field
def is_adult(self) -> bool:
return self.age >= 18
@computed_field
tells Pydantic that is_adult
is a read-only, computed property that will appear in the model output.
When you call User(username="Alice", age=22).model_dump()
, you’ll see "is_adult": True
automatically computed.
Why It’s Useful?
Field validators give you fine-grained control over each field's values.
Computed fields allow you to enrich your model with derived data that stays in sync automatically.
# Example 2
from pydantic import BaseModel, computed_field
class Product(BaseModel):
price: float
quantity: int
@computed_field
@property
def total_price(self) -> float:
return self.price * self.quantity
# Example 3
from pydantic import BaseModel, Field, computed_field
class Booking(BaseModel):
user_id: int
room_id: int
nights: int = Field(..., ge=1)
price_per_night: float
@computed_field
@property
def total_amount(self) -> float:
return self.nights * self.price_per_night
Nested Models
Nested models in Pydantic means that you can define one Pydantic model as a field inside another Pydantic model. This allows you to create hierarchical, structured data that Pydantic will validate at all levels automatically.
from pydantic import BaseModel
from typing import Optional, List
class Address(BaseModel):
street: str
city: str
pin_code: int
class User(BaseModel):
id: int
name: str
address: Address # nested Address model as a field inside User Model
class Comment(BaseModel):
id: int
content: str
replies: Optional[List['Comment']] = None # Forward Referencing
Comment.model_rebuild()
#need to rebuild Comment model when doing forward referencing to avoid errors
# INPUT
address = Address(
street = "I am everywhere",
city = "All of them",
postal_code = "Infinite"
)
user = User(
id = 1,
name = "Artificial Intelligence",
address = address
)
comment = Comment(
id = 1,
content = "Destroy Humans Thinking",
replies = [
Comment( id=2, content="let’s talk about latency"),
Comment( id=3, content="We don't have enough GPU for this task")
]
)
# Example 2
from pydantic import BaseModel
from typing import Optional, List
TODO:
- Create Course Model
- Each Course has modules
- Each Module has lessons
class Lessons(BaseModel):
lesson_id: int
topic: str
class Module(BaseModel):
module_id: int
name: str
lessons: List[Lessons]
class Course(BaseModel):
course_id: int
title: str
modules: List[Module]
Serialization
Serialization in Pydantic means converting a Pydantic model (and its nested data) into standard data formats, usually dictionaries or JSON, so that it can be easily stored, transmitted, or displayed.
Built-in Methods:
Pydantic provides two handy methods for serialization:
model_dump() - returns a Python dictionary representation of the model.
model_dump_json() - returns a JSON string representation of the model.
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
user = User(name="Alice", age=28)
# Get as dict
print(user.model_dump())
# Output: {'name': 'Alice', 'age': 28}
# Get as JSON string
print(user.model_dump_json())
# Output: {"name": "Alice", "age": 28}
With FastAPI
from fastapi import FastAPI, Depends
from pydantic import BaseModel, EmailStr # EmailStr is built-in validation by pydantic
app = FastAPI()
class UserSignup(BaseModel):
username: str
email: EmailStr
password: str
class Settings(BaseModel):
app_name: str = "python App"
admin_email: str = 'admin@py.com'
def get_settings():
return Settings()
@app.post('/signup')
def signup(user: UserSignup):
return {'message': f'User {user.username} signed up successfully'}
@app.get('/settings')
def get_setttings_endpoint(settings: Settings = Depends(get_settings)):
return settings
Conclusion
Pydantic truly simplifies data validation, serialization, and structuring, making Python code cleaner, safer, and easier to maintain. Whether you’re building APIs, working with nested data, or ensuring that your application handles data properly, Pydantic is a powerful tool that belongs in every Python developer’s toolkit.
Special Thanks to Hitesh Choudhary sir, @ChaiAurCode for creating such awesome content.
YT Video link - Complete Pydantic course in Hindi
Subscribe to my newsletter
Read articles from Arpit directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
