Defination
Why use Pydantic
Simple example
Different Pydantic validations
Complete FastAPI example
Defination
Pydantic is used in FastAPI to validate, convert and document incoming and outgoing data.
You define the expected data using a Python class. FastAPI then uses Pydantic automatically to check every request.
Why use Pydantic
Pydantic validation
Powered by type hints — with Pydantic, schema validation and serialization are controlled by type annotations; less to learn, less code to write, and integration with your IDE and static analysis tools. Learn more…
Speed— Pydantic’s core validation logic is written in Rust. As a result, Pydantic is among the fastest data validation libraries for Python. Learn more…
JSON Schema — Pydantic models can emit JSON Schema, allowing for easy integration with other tools. Learn more…
Strict and Lax mode— Pydantic can run in either strict mode (where data is not converted) or lax mode where Pydantic tries to coerce data to the correct type where appropriate. Learn more…
Dataclasses, TypedDicts and more — Pydantic supports validation of many standard library types including dataclass and TypedDict. Learn more…
Customisation — Pydantic allows custom validators and serializers to alter how data is processed in many powerful ways. Learn more…
Ecosystem — around 8,000 packages on PyPI use Pydantic, including massively popular libraries like FastAPI, huggingface, Django Ninja, SQLModel, & LangChain. Learn more…
Battle tested — Pydantic is downloaded over 550M times/month and is used by all FAANG companies and 20 of the 25 largest companies on NASDAQ. If you’re trying to do something with Pydantic, someone else has probably already done it.
Simple example
Suppose your API receives a product order:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Order(BaseModel):
product_name: str
quantity: int
price: float
is_available: bool = True
@app.post("/orders")
def create_order(order: Order):
return {
"message": "Order created successfully",
"order": order
}
Valid request:
{
"product_name": "Laptop",
"quantity": 2,
"price": 55000,
"is_available": true
}
Invalid request:
{
"product_name": "Laptop",
"quantity": "two",
"price": "expensive"
}
FastAPI automatically returns a structured 422 Unprocessable Entity validation response.
Why Pydantic is important in FastAPI
Validates incoming request data.
Converts compatible values into correct Python types.
Produces understandable validation errors.
Generates the OpenAPI and Swagger documentation.
Validates and filters API responses.
Supports nested and complex data structures.
Reduces repetitive manual if conditions.
Improves type hints and editor autocomplete.
Different Pydantic validations
- Data-type validation
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
salary: float
active: bool
Pydantic verifies that every field contains an appropriate value.
- Required and optional fields
from pydantic import BaseModel
class User(BaseModel):
name: str
email: str
phone: str | None = None
name and email are required, while phone is optional.
- Default-value validation
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
available: bool = True
currency: str = "INR"
When the client omits available or currency, Pydantic applies the defaults.
- Minimum and maximum numbers
Use Field() for numeric restrictions:
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str
price: float = Field(gt=0)
quantity: int = Field(ge=1, le=100)
discount: float = Field(ge=0, le=50)
Meaning:
Constraint Meaning
gt=0 Greater than 0
ge=1 Greater than or equal to 1
lt=100 Less than 100
le=100 Less than or equal to 100
- Minimum and maximum string length
from pydantic import BaseModel, Field
class User(BaseModel):
username: str = Field(min_length=3, max_length=20)
password: str = Field(min_length=8, max_length=100)
This prevents usernames or passwords that are too short or long.
- Pattern validation
from pydantic import BaseModel, Field
class User(BaseModel):
username: str = Field(pattern=r"^[a-zA-Z0-9_]+$")
country_code: str = Field(pattern=r"^[A-Z]{2}$")
Examples:
{
"username": "ashwani_123",
"country_code": "IN"
}
Spaces and special characters are rejected from username.
- Email validation
Install the email-validation dependency:
pip install "pydantic[email]"
Then use EmailStr:
from pydantic import BaseModel, EmailStr
class User(BaseModel):
name: str
email: EmailStr
Valid:
{
"name": "Ashwani",
"email": "ashwani@example.com"
}
Invalid:
{
"name": "Ashwani",
"email": "ashwani-example"
}
- URL validation
from pydantic import BaseModel, HttpUrl
class Website(BaseModel):
name: str
url: HttpUrl
Example:
{
"name": "HolidayLandmark",
"url": "https://www.holidaylandmark.com"
}
- Date and time validation
from datetime import date, datetime
from pydantic import BaseModel
class Trip(BaseModel):
title: str
travel_date: date
booking_time: datetime
Example:
{
"title": "Manali Adventure",
"travel_date": "2026-12-15",
"booking_time": "2026-09-10T10:30:00"
}
Pydantic converts these strings into Python date and datetime objects.
- List validation
from pydantic import BaseModel, Field
class Trip(BaseModel):
title: str
destinations: list[str] = Field(min_length=1, max_length=10)
At least one destination must be submitted.
{
"title": "Golden Triangle",
"destinations": ["Delhi", "Agra", "Jaipur"]
}
- Enum validation
Use an enum when only predefined values are allowed:
from enum import Enum
from pydantic import BaseModel
class BookingStatus(str, Enum):
pending = "pending"
confirmed = "confirmed"
cancelled = "cancelled"
class Booking(BaseModel):
customer_name: str
status: BookingStatus
The API accepts only pending, confirmed or cancelled.
- Nested-object validation
from pydantic import BaseModel, EmailStr
class Customer(BaseModel):
name: str
email: EmailStr
class Booking(BaseModel):
trip_id: int
travellers: int
customer: Customer
Request:
{
"trip_id": 501,
"travellers": 2,
"customer": {
"name": "Ashwani",
"email": "ashwani@example.com"
}
}
Pydantic validates both the booking and its nested customer.
- Custom field validation
Use field_validator for custom rules:
from pydantic import BaseModel, field_validator
class User(BaseModel):
username: str
password: str
@field_validator("username")
@classmethod
def username_must_be_lowercase(cls, value: str) -> str:
if value != value.lower():
raise ValueError("Username must contain lowercase letters only")
return value
@field_validator("password")
@classmethod
def validate_password(cls, value: str) -> str:
if not any(character.isdigit() for character in value):
raise ValueError("Password must contain at least one number")
return value
- Cross-field validation
Use model_validator when one field depends on another:
from datetime import date
from pydantic import BaseModel, model_validator
class Trip(BaseModel):
start_date: date
end_date: date
@model_validator(mode="after")
def validate_dates(self):
if self.end_date < self.start_date:
raise ValueError("End date cannot be before start date")
return self
This validation needs both fields, so model_validator is more appropriate than field_validator.
- Decimal and currency validation
For money, prefer Decimal instead of float:
from decimal import Decimal
from pydantic import BaseModel, Field
class Payment(BaseModel):
amount: Decimal = Field(gt=0, decimal_places=2)
currency: str = Field(pattern=r"^[A-Z]{3}$")
Example:
{
"amount": "1499.50",
"currency": "INR"
}
- UUID validation
from uuid import UUID
from pydantic import BaseModel
class BookingRequest(BaseModel):
booking_id: UUID
Invalid UUID formats are rejected automatically.
- Strict validation
By default, Pydantic may convert compatible data. Strict mode prevents unwanted conversion:
from pydantic import BaseModel, ConfigDict
class QuantityRequest(BaseModel):
model_config = ConfigDict(strict=True)
quantity: int
With strict mode:
{
"quantity": "5"
}
is rejected because "5" is a string rather than an integer.
- Preventing unexpected fields
from pydantic import BaseModel, ConfigDict
class LoginRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
email: str
password: str
If the client sends an unexpected field, it is rejected:
{
"email": "user@example.com",
"password": "secret123",
"is_admin": true
}
This is especially valuable for security-sensitive APIs.
Complete FastAPI example
from datetime import date
from decimal import Decimal
from enum import Enum
from fastapi import FastAPI
from pydantic import (
BaseModel,
ConfigDict,
EmailStr,
Field,
HttpUrl,
field_validator,
model_validator,
)
app = FastAPI()
class TripType(str, Enum):
adventure = "adventure"
cultural = "cultural"
leisure = "leisure"
class Customer(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str = Field(min_length=2, max_length=80)
email: EmailStr
age: int = Field(ge=18, le=100)
class TripBooking(BaseModel):
model_config = ConfigDict(extra="forbid")
trip_name: str = Field(min_length=3, max_length=150)
trip_type: TripType
start_date: date
end_date: date
travellers: int = Field(ge=1, le=20)
price: Decimal = Field(gt=0, decimal_places=2)
website: HttpUrl
customer: Customer
coupon_code: str | None = Field(
default=None,
pattern=r"^[A-Z0-9]{4,12}$"
)
@field_validator("trip_name")
@classmethod
def clean_trip_name(cls, value: str) -> str:
return " ".join(value.split())
@model_validator(mode="after")
def validate_trip_dates(self):
if self.end_date < self.start_date:
raise ValueError("End date cannot be before start date")
return self
@app.post("/bookings", response_model=TripBooking)
def create_booking(booking: TripBooking):
return booking
In simple terms, FastAPI manages the API request while Pydantic works like a security guard that checks every piece of data before allowing it into your application.
Top comments (0)