Welcome to my documented journey of learning Pydantic – a powerful library in Python that changed the way I validate, manage, and serialize data in my applications. This guide is structured to help anyone from beginners to advanced developers understand and use Pydantic effectively.
Pydantic is a Python library for data parsing, validation, and serialization using Python type hints.
It ensures that the data you're working with is valid, properly structured, and automatically converted to the correct types. It’s widely used in FastAPI, data pipelines, APIs, and even configuration management.
| Feature | Explanation |
|---|---|
| Runtime Validation | Automatically validates data at runtime based on type hints. |
| Type Conversion | Converts input types (like "123" → 123) when possible. |
| Detailed Errors | Shows clear and helpful error messages when validation fails. |
| Serialization Support | Easily convert models to dictionaries or JSON strings. |
| Nested Models | Supports complex, deeply nested objects. |
| High Performance | Fast and efficient validation. |
| Framework Integration | Works perfectly with modern frameworks like FastAPI. |
All models in Pydantic inherit from BaseModel.
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
is_active: bool = True
user = User(id=1, name="Sourabh", email="sourabh@example.com")
print(user.name) # Sourabh➡️ If wrong types are passed, Pydantic will raise an error.
Use Field() for validation rules, default values, constraints, and metadata.
from pydantic import BaseModel, Field
from typing import Optional
class Product(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0)
quantity: int = Field(default=0, ge=0)
description: Optional[str] = Field(None, max_length=500)➡️ The ... means the field is required.
Create custom validation rules.
from pydantic import BaseModel, validator
class Profile(BaseModel):
username: str
email: str
@validator("username")
def no_space(cls, v):
if " " in v:
raise ValueError("Username must not contain spaces")
return v
@validator("email")
def check_email(cls, v):
if "@" not in v:
raise ValueError("Invalid email")
return vChange model behavior using a nested class:
class User(BaseModel):
name: str
age: int
class Config:
extra = 'forbid' # Do not allow extra fields
validate_assignment = TrueSerialization = Convert models to dictionary or JSON. Deserialization = Create models from dict or JSON.
event_dict = user.model_dump()event_json = user.model_dump_json()User.model_validate_json('{"id": 1, "name": "Alice", "email": "alice@example.com"}')You can use models inside other models.
class Address(BaseModel):
city: str
state: str
class User(BaseModel):
name: str
address: Address
data = {
"name": "Sourabh",
"address": {
"city": "Delhi",
"state": "Delhi"
}
}
user = User(**data)
print(user.address.city) # Delhifrom typing import Optional, Union
from enum import Enum
class UserType(str, Enum):
ADMIN = "admin"
USER = "user"
class User(BaseModel):
name: str
age: Optional[int]
user_type: UserType = UserType.USER
data: Union[dict, str, None]Great for .env file and secret management.
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str
debug: bool = False
class Config:
env_file = ".env"
settings = Settings()
print(settings.app_name)from pydantic import BaseModel, Field, validator
from typing import List, Optional
from datetime import datetime, date
from enum import Enum
class Gender(str, Enum):
MALE = "male"
FEMALE = "female"
OTHER = "other"
class Address(BaseModel):
city: str
state: str
pin: str
class MedicalHistory(BaseModel):
condition: str
diagnosed_date: date
class Patient(BaseModel):
name: str
age: int
gender: Gender
address: Address
medical_history: List[MedicalHistory] = []
registered_at: datetime = Field(default_factory=datetime.now)
@validator("age")
def validate_age(cls, v):
if v < 0:
raise ValueError("Age must be positive")
return vfrom pydantic import ValidationError
try:
user = User(id="abc", name="123", email="notemail")
except ValidationError as e:
print(e.json(indent=2))| Pydantic v1 | Pydantic v2 Equivalent |
|---|---|
.dict() |
.model_dump() |
.json() |
.model_dump_json() |
parse_obj() |
model_validate() |
@validator |
@field_validator (v2) |
| Use Case | How Pydantic Helps |
|---|---|
| 🧾 API Development | Validates request/response data (e.g., FastAPI) |
| 🧹 Data Cleaning | Converts raw data into clean, typed data |
| ⚙️ Config Management | Loads environment variables with BaseSettings |
| 🔒 Secure Apps | Validates user input & prevents bad data handling |
- Use
slots = TrueinConfigfor memory-efficient models. - Use
exclude_unset=Truefor clean serialized output. - Avoid using complex custom validators when simple types work.
- Reuse models where possible.
- ✅ Always use clear type annotations.
- 🚫 Avoid unnecessary custom validation.
- 💬 Use
Field()for metadata and constraints. - 🧠 Separate model structure from business logic.
- ⚙️ Use
BaseSettingsfor config. - 🔐 Catch and handle
ValidationErrorgracefully. - 🧩 Use nested models for clarity in complex data.
| Stage | What I Learned |
|---|---|
| 🟢 Beginner | What is Pydantic, BaseModel, Field(), Validation |
| 🔵 Intermediate | Nested Models, JSON serialization, Error handling |
| 🔴 Advanced | Custom validators, Settings, Enums, Best practices |
- 📘 Official Docs: https://docs.pydantic.dev
- 🚀 FastAPI + Pydantic: https://fastapi.tiangolo.com
- 🧪 GitHub Repo: https://github.com/pydantic/pydantic
Pydantic taught me how powerful and safe typed data handling can be. This journey not only improved my code but also made me think deeper about structure, clarity, and validation in every Python project I build.
– Sourabh Kumar
🌟 Happy Learning! If this helped you, feel free to build upon it and share it with others.