| name | pydantic |
| description | Data validation using Python type hints with Pydantic models, settings, serialization, and performance optimization |
| metadata | {"author":"mte90","version":"1.0.0","tags":["python","validation","pydantic","serialization","settings"]} |
Pydantic
Data validation using Python type annotations.
Overview
Pydantic is a Python library that provides data validation and settings management using Python type annotations. It validates data at runtime and generates JSON Schema for automatic documentation.
Key Features:
- Data validation using type hints
- Automatic data coercion
- JSON Schema generation
- Serialization/deserialization
- Settings management with environment variables
- Fast performance (especially v2)
- Extensive customization options
Installation
pip install pydantic
pip install pydantic[email]
pip install pydantic[email] orjson
pip install pydantic>=2.0.0
Basic Models
Creating Models
from pydantic import BaseModel, Field
from typing import Optional, List
class User(BaseModel):
"""Basic user model."""
id: int
name: str
email: str
age: Optional[int] = None
is_active: bool = True
user = User(id=1, name="John", email="john@example.com")
print(user)
data = {
"id": 2,
"name": "Jane",
"email": "jane@example.com",
"age": 25
}
user = User(**data)
json_data = '{"id": 3, "name": "Bob", "email": "bob@example.com"}'
user = User.model_validate_json(json_data)
Field Types
from pydantic import BaseModel
from typing import Optional, List, Dict, Set, Tuple, Union, Any
class AllTypesExample(BaseModel):
string: str
integer: int
floating: float
boolean: bool
bytes: bytes
optional_string: Optional[str] = None
optional_with_default: Optional[int] = 42
list_of_strings: List[str] = []
list_of_ints: List[int]
dict_data: Dict[str, int] = {}
set_of_ints: Set[int] = set()
tuple_data: Tuple[int, str, float] = (1, "a", 1.5)
union_field: Union[int, str] = 1
any_field: Any = "anything"
from typing import Literal
status: Literal["pending", "active", "completed"] = "pending"
Nested Models
from pydantic import BaseModel, Field
from typing import List, Optional
class ChildModel(BaseModel):
"""Nested child model with alias for input."""
child_id: int
child_name: str
class ParentModel(BaseModel):
"""Parent containing child model with Field alias."""
parent_id: int
parent_name: str
child_data: ChildModel = Field(alias='child_data')
def __init__(self, parent_id: int, parent_name: str, child_info: ChildModel = None, **data):
super().__init__(
parent_id=parent_id,
parent_name=parent_name,
child_data=child_info if child_info else data.get('child_data')
)
class Person(BaseModel):
name: str
email: str
address: Optional[Address] = None
company: Optional[Company] = None
person = Person(
name="John",
email="john@example.com",
address=Address(
street="123 Main St",
city="New York",
country="USA"
),
company=Company(
name="Tech Corp",
address=Address(
street="456 Business Ave",
city="San Francisco",
country="USA"
),
employees=["Alice", "Bob"]
)
)
Field Validation
Field Constraints
from pydantic import BaseModel, Field, field_validator
from typing import Optional
import re
class ConstrainedUser(BaseModel):
name: str = Field(min_length=1, max_length=100)
username: str = Field(pattern=r'^[a-zA-Z0-9_]+$')
email: str = Field(format="email")
age: int = Field(ge=0, le=150)
price: float = Field(gt=0)
quantity: int = Field(ge=0, default=0)
tags: List[str] = Field(min_length=1, max_length=10)
scores: List[int] = Field(min_length=1, max_length=100)
nickname: Optional[str] = Field(default=None, max_length=50)
user = ConstrainedUser(
name="John Doe",
username="john_doe",
email="john@example.com",
age=25,
price=19.99,
tags=["python", "fastapi"]
)
Field Validators
from pydantic import BaseModel, field_validator, Field
import re
class ValidatedUser(BaseModel):
username: str
password: str
age: int
@field_validator('username')
@classmethod
def validate_username(cls, v: str) -> str:
if len(v) < 3:
raise ValueError('Username must be at least 3 characters')
if not re.match(r'^[a-zA-Z0-9_]+$', v):
raise ValueError('Username can only contain letters, numbers, and underscores')
return v
@field_validator('password')
@classmethod
def validate_password(cls, v: str) -> str:
if len(v) < 8:
raise ValueError('Password must be at least 8 characters')
if not re.search(r'[A-Z]', v):
raise ValueError('Password must contain at least one uppercase letter')
if not re.search(r'[0-9]', v):
raise ValueError('Password must contain at least one digit')
return v
@field_validator('age')
@classmethod
def validate_age(cls, v: int) -> int:
if v < 0 or v > 150:
raise ValueError('Age must be between 0 and 150')
return v
class MultiValidatedField(BaseModel):
value: str
@field_validator('value')
@classmethod
def strip_value(cls, v: str) -> str:
return v.strip()
@field_validator('value')
@classmethod
def validate_not_empty(cls, v: str) -> str:
if not v:
raise ValueError('Value cannot be empty')
return v
Model Validators
from pydantic import BaseModel, model_validator, field_validator
from typing import Optional
class UserRegistration(BaseModel):
password: str
confirm_password: str
username: str
@model_validator(mode='before')
@classmethod
def check_passwords_match(cls, data):
"""Validate before any field validation."""
if isinstance(data, dict):
if data.get('password') != data.get('confirm_password'):
raise ValueError('Passwords do not match')
return data
@model_validator(mode='after')
def validate_username_not_password(self):
"""Validate after all field validation."""
if self.username.lower() in self.password.lower():
raise ValueError('Username cannot be part of the password')
return self
class AdvancedValidation(BaseModel):
start_date: str
end_date: str
@model_validator(mode='after')
def validate_dates(self):
from datetime import datetime
start = datetime.fromisoformat(self.start_date)
end = datetime.fromisoformat(self.end_date)
if end < start:
raise ValueError('End date must be after start date')
return self
Serialization
Model Dump
from pydantic import BaseModel
from typing import List, Optional
class User(BaseModel):
id: int
name: str
email: str
tags: List[str] = []
metadata: Optional[dict] = None
user = User(
id=1,
name="John",
email="john@example.com",
tags=["admin", "developer"],
metadata={"department": "Engineering"}
)
data = user.model_dump()
print(data)
json_str = user.model_dump_json()
print(json_str)
data = user.model_dump(include={'id', 'name'})
data = user.model_dump(exclude={'metadata'})
data = user.model_dump(exclude_none=True)
from pydantic import TypeAdapter
ta = TypeAdapter(List[Optional[str]])
clean_data = ta.dump_json(["a", None, "b"])
Advanced Serialization
from pydantic import BaseModel, Field, SerializerFunctionWrap
from typing import List, Optional
from datetime import datetime
class User(BaseModel):
id: int
name: str
created_at: datetime
@property
def display_name(self) -> str:
return f"#{self.id} - {self.name}"
@field_serializer('created_at')
def serialize_datetime(self, dt: datetime) -> str:
return dt.isoformat()
user = User(id=1, name="John", created_at=datetime.now())
data = user.model_dump(
mode='json',
include={'id', 'name'},
exclude={'created_at'},
by_alias=True,
exclude_none=True,
exclude_unset=True,
exclude_defaults=True
)
def dynamic_schema_gen():
schema = {
"type": "object",
"properties": {"id": {"type": "integer"}, "name": {"type": "string"}}
}
User.model_rebuild()
return User
Model Validate
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
id: int
name: str
email: str
data = {"id": 1, "name": "John", "email": "john@example.com"}
user = User.model_validate(data)
json_str = '{"id": 2, "name": "Jane", "email": "jane@example.com"}'
user = User.model_validate_json(json_str)
class SomeClass:
def __init__(self):
self.id = 3
self.name = "Bob"
self.email = "bob@example.com"
obj = SomeClass()
user = User.model_validate(obj)
data = {"name": "Updated Name"}
user = User.model_validate(data, update={"id": 1, "email": "old@example.com"})
JSON Schema
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
schema = User.model_json_schema()
print(schema)
class ConfiguredUser(BaseModel):
model_config = {'title': 'User Model', 'description': 'A user model'}
id: int
name: str
schema = ConfiguredUser.model_json_schema()
Alias and Naming
Field Aliases
from pydantic import BaseModel, Field, AliasChoices
class UserWithAlias(BaseModel):
user_id: int = Field(alias='userId')
first_name: str = Field(alias='firstName')
last_name: str = Field(alias='lastName')
email_address: str = Field(validation_alias='email')
data = {"userId": 1, "firstName": "John", "lastName": "Doe", "email": "john@example.com"}
user = UserWithAlias.model_validate(data)
print(user.user_id)
print(user.first_name)
json_data = user.model_dump(by_alias=True)
Alias Generator
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel, to_snake, to_pascal
class CamelCaseUser(BaseModel):
"""User with camelCase serialization."""
model_config = ConfigDict(alias_generator=to_camel)
user_id: int
first_name: str
last_name: str
email_address: str
user = CamelCaseUser(
user_id=1,
first_name="John",
last_name="Doe",
email_address="john@example.com"
)
print(user.model_dump(by_alias=True))
Default Values
Field Defaults
from pydantic import BaseModel, Field
from typing import Optional, List
class DefaultsExample(BaseModel):
name: str = "Unknown"
age: int = 25
nickname: Optional[str] = None
email: str
tags: List[str] = Field(default_factory=list)
items: List[int] = Field(default_factory=lambda: [1, 2, 3])
metadata: dict = Field(default_factory=dict)
scores: List[int] = Field(default_factory=list)
import uuid
class WithFactory(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
created_at: str = Field(default_factory=lambda: "generated_value")
Default Values with Validators
from pydantic import BaseModel, field_validator, Field
from typing import Optional
class UserWithDefaultValidator(BaseModel):
username: str
display_name: str = Field(default="")
@field_validator('display_name', mode='before')
@classmethod
def use_username_as_display_name(cls, v, info):
if v == "":
return info.data.get('username', 'Unknown')
return v
user = UserWithDefaultValidator(username="john")
print(user.display_name)
user = UserWithDefaultValidator(username="john", display_name="John Doe")
print(user.display_name)
Constrained Types
String Constraints
from pydantic import BaseModel, Field, constr
class FieldConstraints(BaseModel):
username: str = Field(min_length=3, max_length=20)
password: str = Field(min_length=8)
slug: str = Field(pattern=r'^[a-z0-9-]+$')
class ConstrainedTypes(BaseModel):
username: constr(min_length=3, max_length=20)
password: constr(min_length=8)
slug: constr(pattern=r'^[a-z0-9-]+$')
email: constr(pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$')
user = ConstrainedTypes(
username="john_doe",
password="secret123",
slug="my-blog-post",
email="john@example.com"
)
Numeric Constraints
from pydantic import BaseModel, Field, conint, confloat, conlist
class NumericConstraints(BaseModel):
quantity: conint(ge=0, le=1000)
age: conint(ge=0, le=150)
price: confloat(gt=0)
rating: confloat(ge=0.0, le=5.0)
scores: conlist(int, min_length=1, max_length=10)
codes: conlist(str, min_length=3)
product = NumericConstraints(
quantity=10,
age=25,
price=19.99,
rating=4.5,
scores=[90, 85, 95],
codes=["ABC", "DEF"]
)
Special Types
Email, URL, UUID
from pydantic import BaseModel, EmailStr, HttpUrl, UUID1, UUID4, PaymentCardNumber
from typing import Optional
import uuid
class ContactInfo(BaseModel):
email: EmailStr
secondary_email: Optional[EmailStr] = None
website: HttpUrl
api_endpoint: Optional[HttpUrl] = None
user_uuid: UUID1
session_uuid: UUID4
card_number: Optional[PaymentCardNumber] = None
contact = ContactInfo(
email="user@example.com",
website="https://example.com",
user_uuid=uuid.uuid1(),
session_uuid=uuid.uuid4()
)
Secret Types
from pydantic import BaseModel, SecretStr, SecretBytes
class Credentials(BaseModel):
password: SecretStr
api_key: Optional[SecretStr] = None
encryption_key: SecretBytes
creds = Credentials(password="secret123")
print(creds.password)
print(creds.password.get_secret_value())
data = creds.model_dump()
Enum Types
from pydantic import BaseModel, StrEnum, IntEnum
from enum import Enum
class Status(str, Enum):
PENDING = "pending"
ACTIVE = "active"
COMPLETED = "completed"
FAILED = "failed"
class Priority(int, Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
class Task(BaseModel):
status: Status = Status.PENDING
priority: Priority = Priority.MEDIUM
task = Task(status=Status.ACTIVE, priority=Priority.HIGH)
class UserRole(StrEnum):
ADMIN = "admin"
USER = "user"
GUEST = "guest"
class User(BaseModel):
role: UserRole = UserRole.GUEST
Configuration
Model Config
from pydantic import BaseModel, ConfigDict
from typing import Optional
class User(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
arbitrary_types_allowed=True,
use_enum_values=True,
populate_by_name=True,
extra='forbid',
frozen=True,
strict_fields=False,
)
id: int
name: str
email: Optional[str] = None
class UserV1(BaseModel):
class Config:
str_strip_whitespace = True
validate_assignment = True
id: int
name: str
Field-Level Config
from pydantic import BaseModel, Field, field_config
@field_config(validate_default=True)
def validated_field():
return Field(default="default_value")
class ModelWithFieldConfig(BaseModel):
value: str = Field(default="test", validate_default=True)
Advanced Config Patterns
Strict Mode and Extra Fields
from pydantic import BaseModel, ConfigDict, Field
from typing import Any, Optional
class StrictWithExtra(BaseModel):
model_config = ConfigDict(
strict=False,
extra="allow"
)
id: int
name: str
metadata: Optional[dict] = None
extra_data: Any = Field(default=None)
data = {"id": 1, "name": "John", "extra_field": "allowed"}
user = StrictWithExtra(**data)
print(user.extra_field)
class StrictModel(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")
id: int
name: str
data = {"id": 1, "name": "John", "extra": "not allowed"}
Multi-Environment Settings
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
import os
class DatabaseSettings(BaseSettings):
"""Database configuration."""
model_config = SettingsConfigDict(
env_file='.env.prod',
env_nested_delimiter='__',
)
host: str = "localhost"
port: int = 5432
name: str
user: str
password: str
class DevSettings(BaseSettings):
"""Development settings."""
model_config = SettingsConfigDict(
env_file='.env.dev',
)
debug: bool = True
host: str = "127.0.0.1"
port: int = 8000
class ProdSettings(BaseSettings):
"""Production settings."""
model_config = SettingsConfigDict(
env_file='.env.prod',
)
debug: bool = False
host: str = os.getenv("DATABASE_HOST", "db.prod.example.com")
port: int = 5432
env = os.getenv("ENVIRONMENT", "dev")
if env == "prod":
settings = ProdSettings()
else:
settings = DevSettings()
Nested Structure Config
from pydantic import BaseModel, ConfigDict
from typing import Dict, Any, Optional
class APIConfig(BaseModel):
"""Nested API configuration."""
model_config = ConfigDict(
extra="allow",
)
timeout: int = 30
retries: int = 3
endpoints: Dict[str, Any] = {}
class Application(BaseModel):
"""Application with nested config structures."""
model_config = ConfigDict(
str_strip_whitespace=True,
)
app_name: str
version: str
api: APIConfig = Field(default_factory=APIConfig)
features: Dict[str, bool] = {}
app = Application(
app_name="MyApp",
version="1.0.0",
api={"timeout": 60, "retries": 5, "custom_setting": "extra"}
)
TypeAdapter
TypeAdapter for Custom Validation
from pydantic import TypeAdapter, ValidationError
from typing import List, Optional, Any
ta_list_str = TypeAdapter(List[str])
data = ["a", "b", "c"]
validated = ta_list_str.validate_python(data)
print(validated)
json_data = '["x", "y", "z"]'
validated_json = ta_list_str.validate_json(json_data)
print(validated_json)
output = ta_list_str.dump_json(["hello", "world"])
print(output)
ta_int = TypeAdapter(int)
result = ta_int.validate_python("42")
print(result)
from typing import Dict
ta_dict = TypeAdapter(Dict[str, int])
nested_data = {"a": 1, "b": 2, "c": 3}
validated_nested = ta_dict.validate_python(nested_data)
class CustomIntError(BaseException):
pass
ta_custom = TypeAdapter(int)
try:
ta_custom.validate_python("not a number")
except ValidationError as e:
print(f"Validation failed: {e.errors()[0]['msg']}")
RootModel
RootModel for Collection-Only Schemas
from pydantic import RootModel
from typing import List, Optional
class StringList(RootModel[List[str]]):
"""Root model for list of strings - no parent wrapper."""
@property
def labels(self) -> List[str]:
"""Derived property for list items."""
return [item.upper() for item in self.root]
strings = StringList(root=["apple", "banana", "cherry"])
print(strings.root)
print(strings.labels)
json_data = '["dog", "cat", "bird"]'
pets = StringList.model_validate_json(json_data)
print(pets)
class OptionalList(RootModel[Optional[List[str]]]):
"""Root model with optional root."""
pass
optional = OptionalList(root=["a", "b"])
empty = OptionalList(root=None)
print(optional.root)
print(empty.root)
class NumberList(RootModel[List[int]]):
"""List of numbers."""
pass
numbers = NumberList(root=[1, 2, 3])
print(numbers.model_dump())
Discriminated Unions
Basic Discriminated Union
from pydantic import BaseModel, Field, Discriminator
from typing import Union, Annotated
class Cat(BaseModel):
pet_type: str = Field(default="cat", const=True)
meows: int
class Dog(BaseModel):
pet_type: str = Field(default="dog", const=True)
barks: float
class Zoo(BaseModel):
pet: Union[Cat, Dog] = Field(..., discriminator='pet_type')
class ZooV2(BaseModel):
pet: Annotated[
Union[Cat, Dog],
Discriminator(tag='pet_type')
]
class ApiResponse(BaseModel):
status: str
data: Union[Cat, Dog] = Field(..., discriminator='pet_type')
response = ApiResponse(
status="success",
data={"pet_type": "cat", "meows": 5}
)
print(response.data)
Advanced Discriminated Union
from pydantic import BaseModel, Field, Discriminator
from typing import Union, Literal, Annotated
class Image(BaseModel):
type: Literal["image"]
url: str
format: Literal["jpg", "png", "gif"]
class Video(BaseModel):
type: Literal["video"]
url: str
duration: int
format: Literal["mp4", "webm"]
class Document(BaseModel):
type: Literal["document"]
pages: int
format: Literal["pdf", "docx"]
class MediaResponse(BaseModel):
id: int
title: str
media: Annotated[
Union[Image, Video, Document],
Discriminator(tag='type')
]
created_at: str
response = MediaResponse(
id=1,
title="My Video",
media={"type": "video", "url": "https://example.com/video.mp4", "duration": 120, "format": "mp4"},
created_at="2024-01-01T00:00:00Z"
)
print(response.media)
Error Handling
Error Handling with ValidationError
from pydantic import BaseModel, ValidationError
from typing import List, Optional
class UserResponse(BaseModel):
id: int
username: str
email: str
age: int
def fetch_user_from_api(api_data):
try:
user = UserResponse(**api_data)
return {"success": True, "data": user}
except ValidationError as e:
errors = e.errors()
formatted_errors = {
error['loc'][0]: {
'message': error['msg'],
'type': error['type'],
'input': error['input']
}
for error in errors
if error['loc'] and len(error['loc']) > 0
}
return {
"success": False,
"errors": formatted_errors
}
valid_data = {"id": 1, "username": "john", "email": "john@example.com", "age": 30}
invalid_data = {"id": "not_an_int", "username": "jo", "email": "invalid", "age": -5}
result = fetch_user_from_api(valid_data)
print(result["success"])
result = fetch_user_from_api(invalid_data)
print(result["success"])
print(result["errors"])
try:
user = UserResponse(id="not an int", name="John", age="not an int")
except ValidationError as e:
print(e.errors())
print(e)
from pydantic import field_validator
class AgeUser(BaseModel):
age: int
@field_validator('age')
@classmethod
def validate_age(cls, v):
if v < 0:
raise ValueError('Age must be a positive number')
if v > 150:
raise ValueError('Are you really that old?')
return v
try:
AgeUser(age=-5)
except ValidationError as e:
print(e.error_count())
for error in e.errors():
print(f"Field: {error['loc']}")
print(f"Message: {error['msg']}")
print(f"Input: {error['input']}")
Settings
BaseSettings
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
class Settings(BaseSettings):
"""Application settings from environment variables."""
app_name: str
database_url: str
debug: bool = False
port: int = 8000
max_connections: int = 10
secret_key: str
db_host: str = Field(alias='DATABASE_HOST', default='localhost')
settings = Settings(
app_name="My App",
database_url="postgresql://localhost/mydb",
secret_key="super-secret"
)
print(settings.app_name)
print(settings.debug)
class SettingsV2(BaseSettings):
model_config = SettingsConfigDict(
env_file='.env',
env_file_encoding='utf-8',
case_sensitive=False,
extra='ignore'
)
app_name: str
debug: bool = False
Nested Settings
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
class DatabaseSettings(BaseSettings):
"""Database configuration."""
host: str = "localhost"
port: int = 5432
name: str
user: str
password: str
class RedisSettings(BaseSettings):
"""Redis configuration."""
host: str = "localhost"
port: int = 6379
db: int = 0
class Settings(BaseSettings):
"""Application settings."""
app_name: str
database: DatabaseSettings
redis: RedisSettings
model_config = SettingsConfigDict(env_nested_delimiter='__')
settings = Settings(
app_name="MyApp",
database={"name": "mydb", "user": "user", "password": "pass"},
redis={}
)
FastAPI Integration
Request Models
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel, EmailStr, Field
app = FastAPI()
class UserCreate(BaseModel):
"""User creation schema."""
username: str = Field(min_length=3, max_length=50)
email: EmailStr
password: str = Field(min_length=8)
age: int = Field(ge=0, le=150)
class UserResponse(BaseModel):
"""User response schema (excludes sensitive data)."""
id: int
username: str
email: str
model_config = {'from_attributes': True}
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
"""Create a new user."""
new_user = await save_user(user.model_dump())
return new_user
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
"""Get user by ID."""
user = await get_user_by_id(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
Response Models
from fastapi import FastAPI
from pydantic import BaseModel
from datetime import datetime
class Item(BaseModel):
id: int
name: str
price: float
class ItemWithTax(BaseModel):
"""Item with calculated tax."""
id: int
name: str
price: float
tax: float
@classmethod
def from_item(cls, item: Item):
return cls(
id=item.id,
name=item.name,
price=item.price,
tax=item.price * 0.1
)
app = FastAPI()
@app.get("/items/{item_id}", response_model=ItemWithTax)
async def get_item(item_id: int):
item = await get_item_from_db(item_id)
return ItemWithTax.from_item(item)
Nested Validation
from fastapi import FastAPI
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Address(BaseModel):
street: str
city: str
country: str
class UserWithAddress(BaseModel):
name: str
address: Address
@app.post("/users")
async def create_user(user: UserWithAddress):
return user
Best Practices
1. Use Type Hints
class User(BaseModel):
id: int
name: str
email: str
is_active: bool = True
class User(BaseModel):
id = None
name = None
2. Define Defaults Properly
class User(BaseModel):
tags: List[str] = Field(default_factory=list)
metadata: dict = Field(default_factory=dict)
class User(BaseModel):
tags: List[str] = []
metadata: dict = {}
3. Use Constrained Types
class User(BaseModel):
username: str = Field(min_length=3, max_length=20)
age: int = Field(ge=0, le=150)
class User(BaseModel):
username: str
age: int
@field_validator('username')
def validate_username(self, v):
if len(v) < 3 or len(v) > 20:
raise ValueError('Invalid username')
return v
4. Separate Schemas
class UserBase(BaseModel):
email: EmailStr
class UserCreate(UserBase):
username: str
password: str
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
username: Optional[str] = None
class UserResponse(UserBase):
id: int
created_at: datetime
model_config = {'from_attributes': True}
Common Issues
Performance
import orjson
class FastModel(BaseModel):
model_config = {'json_loads': orjson.loads, 'json_dumps': orjson.dumps}
Mutable Defaults
class BadModel(BaseModel):
items: list = []
class GoodModel(BaseModel):
items: list = Field(default_factory=list)
Validation Order
class OrderedValidation(BaseModel):
value: str
@field_validator('value', mode='before')
@classmethod
def before_validation(cls, v):
return v.strip().lower() if isinstance(v, str) else v
@field_validator('value')
@classmethod
def after_validation(cls, v):
return v
References