pydantic

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pydantic Validation Skill

Pydantic验证技能

Summary

概述

Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation.
借助Pydantic v2的Rust驱动核心,使用类型提示和运行时类型检查实现高性能的Python数据验证。

When to Use

适用场景

  • API request/response validation (FastAPI, Django)
  • Settings and configuration management (env variables, config files)
  • ORM model validation (SQLAlchemy integration)
  • Data parsing and serialization (JSON, dict, custom formats)
  • Type-safe data classes with automatic validation
  • CLI argument parsing with type safety
  • API请求/响应验证(FastAPI、Django)
  • 设置与配置管理(环境变量、配置文件)
  • ORM模型验证(SQLAlchemy集成)
  • 数据解析与序列化(JSON、字典、自定义格式)
  • 带自动验证的类型安全数据类
  • 带类型安全的CLI参数解析

Quick Start

快速开始

python
from pydantic import BaseModel, Field, EmailStr
from datetime import datetime

class User(BaseModel):
    id: int
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    created_at: datetime = Field(default_factory=datetime.now)
    is_active: bool = True
python
from pydantic import BaseModel, Field, EmailStr
from datetime import datetime

class User(BaseModel):
    id: int
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    created_at: datetime = Field(default_factory=datetime.now)
    is_active: bool = True

Validate data

验证数据

user = User(id=1, name="Alice", email="alice@example.com") print(user.model_dump()) # {'id': 1, 'name': 'Alice', ...}
user = User(id=1, name="Alice", email="alice@example.com") print(user.model_dump()) # {'id': 1, 'name': 'Alice', ...}

Automatic type coercion

自动类型转换

user2 = User(id="2", name="Bob", email="bob@example.com") assert user2.id == 2 # String "2" coerced to int
user2 = User(id="2", name="Bob", email="bob@example.com") assert user2.id == 2 # 字符串"2"转换为整数

Validation error

验证错误

try: User(id=3, name="", email="invalid") except ValidationError as e: print(e.errors())

---
try: User(id=3, name="", email="invalid") except ValidationError as e: print(e.errors())

---

Core Concepts

核心概念

BaseModel Foundation

BaseModel基础

python
from pydantic import BaseModel, ConfigDict

class Product(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        use_enum_values=True,
        arbitrary_types_allowed=False
    )

    name: str
    price: float
    quantity: int = 0
python
from pydantic import BaseModel, ConfigDict

class Product(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        use_enum_values=True,
        arbitrary_types_allowed=False
    )

    name: str
    price: float
    quantity: int = 0

Usage

使用示例

product = Product(name=" Widget ", price=19.99) assert product.name == "Widget" # Whitespace stripped
product = Product(name=" Widget ", price=19.99) assert product.name == "Widget" # 自动去除空格

Validate on assignment

赋值时验证

product.price = "29.99" # Auto-converts to float
undefined
product.price = "29.99" # 自动转换为浮点数
undefined

Field Configuration

字段配置

python
from pydantic import Field, field_validator
from typing import Annotated

class Item(BaseModel):
    # Field constraints
    sku: str = Field(pattern=r'^[A-Z]{3}-\d{4}$')
    price: float = Field(gt=0, le=10000)
    stock: int = Field(ge=0, default=0)

    # Annotated types (Pydantic v2)
    quantity: Annotated[int, Field(ge=1, le=100)]

    # Descriptions and examples
    description: str = Field(
        ...,
        description="Product description",
        examples=["High-quality widget"]
    )

    # Deprecated fields
    old_field: str | None = Field(None, deprecated=True)

    @field_validator('sku')
    @classmethod
    def validate_sku(cls, v: str) -> str:
        if not v.startswith('ABC'):
            raise ValueError('SKU must start with ABC')
        return v
python
from pydantic import Field, field_validator
from typing import Annotated

class Item(BaseModel):
    # 字段约束
    sku: str = Field(pattern=r'^[A-Z]{3}-\d{4}$')
    price: float = Field(gt=0, le=10000)
    stock: int = Field(ge=0, default=0)

    # 注解类型(Pydantic v2)
    quantity: Annotated[int, Field(ge=1, le=100)]

    # 描述与示例
    description: str = Field(
        ...,
        description="产品描述",
        examples=["高品质小部件"]
    )

    # 已废弃字段
    old_field: str | None = Field(None, deprecated=True)

    @field_validator('sku')
    @classmethod
    def validate_sku(cls, v: str) -> str:
        if not v.startswith('ABC'):
            raise ValueError('SKU必须以ABC开头')
        return v

Pydantic v2 Improvements

Pydantic v2 改进点

Migration from v1

从v1迁移

python
undefined
python
undefined

Pydantic v1

Pydantic v1

class OldModel(BaseModel): class Config: validate_assignment = True json_encoders = {datetime: lambda v: v.isoformat()}
class OldModel(BaseModel): class Config: validate_assignment = True json_encoders = {datetime: lambda v: v.isoformat()}

Pydantic v2

Pydantic v2

class NewModel(BaseModel): model_config = ConfigDict( validate_assignment=True, # json_encoders replaced by serializers )
@model_serializer
def ser_model(self) -> dict:
    return {...}
class NewModel(BaseModel): model_config = ConfigDict( validate_assignment=True, # json_encoders 被序列化器替代 )
@model_serializer
def ser_model(self) -> dict:
    return {...}

Key changes:

主要变更:

- .dict() → .model_dump()

- .dict() → .model_dump()

- .json() → .model_dump_json()

- .json() → .model_dump_json()

- .parse_obj() → .model_validate()

- .parse_obj() → .model_validate()

- .parse_raw() → .model_validate_json()

- .parse_raw() → .model_validate_json()

- @validator → @field_validator

- @validator → @field_validator

- @root_validator → @model_validator

- @root_validator → @model_validator

undefined
undefined

Performance Improvements

性能提升

python
undefined
python
undefined

v2 uses Rust core (pydantic-core) for 5-50x speedup

v2 使用Rust核心(pydantic-core)实现5-50倍的速度提升

from pydantic import BaseModel import time
class Data(BaseModel): values: list[int] names: list[str]
from pydantic import BaseModel import time
class Data(BaseModel): values: list[int] names: list[str]

Benchmark

基准测试

data = {'values': list(range(10000)), 'names': ['item'] * 10000} start = time.perf_counter() for _ in range(1000): Data.model_validate(data) elapsed = time.perf_counter() - start print(f"Validated 1000 iterations in {elapsed:.2f}s")
undefined
data = {'values': list(range(10000)), 'names': ['item'] * 10000} start = time.perf_counter() for _ in range(1000): Data.model_validate(data) elapsed = time.perf_counter() - start print(f"完成1000次验证耗时 {elapsed:.2f}秒")
undefined

Field Types

字段类型

Built-in Types

内置类型

python
from pydantic import (
    BaseModel, EmailStr, HttpUrl, UUID4,
    FilePath, DirectoryPath, Json, SecretStr,
    PositiveInt, NegativeFloat, conint, constr
)
from typing import Literal
from pathlib import Path

class Example(BaseModel):
    # Email validation
    email: EmailStr

    # URL validation
    website: HttpUrl

    # UUID
    id: UUID4

    # File system paths
    config_file: FilePath
    data_dir: DirectoryPath

    # JSON string → parsed object
    metadata: Json[dict[str, str]]

    # Secret (won't print in logs)
    api_key: SecretStr

    # Constrained types
    age: PositiveInt
    balance: NegativeFloat
    username: constr(min_length=3, max_length=20, pattern=r'^[a-z]+$')
    code: conint(ge=1000, le=9999)

    # Literal types
    status: Literal['pending', 'approved', 'rejected']
python
from pydantic import (
    BaseModel, EmailStr, HttpUrl, UUID4,
    FilePath, DirectoryPath, Json, SecretStr,
    PositiveInt, NegativeFloat, conint, constr
)
from typing import Literal
from pathlib import Path

class Example(BaseModel):
    # 邮箱验证
    email: EmailStr

    # URL验证
    website: HttpUrl

    # UUID
    id: UUID4

    # 文件系统路径
    config_file: FilePath
    data_dir: DirectoryPath

    # JSON字符串 → 解析对象
    metadata: Json[dict[str, str]]

    # 保密字段(日志中不会打印)
    api_key: SecretStr

    # 约束类型
    age: PositiveInt
    balance: NegativeFloat
    username: constr(min_length=3, max_length=20, pattern=r'^[a-z]+$')
    code: conint(ge=1000, le=9999)

    # 字面量类型
    status: Literal['pending', 'approved', 'rejected']

Custom Types

自定义类型

python
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import core_schema
from typing import Any

class Color:
    def __init__(self, r: int, g: int, b: int):
        self.r, self.g, self.b = r, g, b

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls.validate,
            core_schema.str_schema()
        )

    @classmethod
    def validate(cls, v: str) -> 'Color':
        if not v.startswith('#') or len(v) != 7:
            raise ValueError('Invalid hex color')
        r = int(v[1:3], 16)
        g = int(v[3:5], 16)
        b = int(v[5:7], 16)
        return cls(r, g, b)

class Design(BaseModel):
    primary_color: Color
python
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import core_schema
from typing import Any

class Color:
    def __init__(self, r: int, g: int, b: int):
        self.r, self.g, self.b = r, g, b

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls.validate,
            core_schema.str_schema()
        )

    @classmethod
    def validate(cls, v: str) -> 'Color':
        if not v.startswith('#') or len(v) != 7:
            raise ValueError('无效的十六进制颜色值')
        r = int(v[1:3], 16)
        g = int(v[3:5], 16)
        b = int(v[5:7], 16)
        return cls(r, g, b)

class Design(BaseModel):
    primary_color: Color

Usage

使用示例

design = Design(primary_color='#FF5733') assert design.primary_color.r == 255
undefined
design = Design(primary_color='#FF5733') assert design.primary_color.r == 255
undefined

Validators

验证器

Field Validators

字段验证器

python
from pydantic import field_validator, model_validator

class Account(BaseModel):
    username: str
    password: str
    password_confirm: str

    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError('must be alphanumeric')
        return v

    @field_validator('password')
    @classmethod
    def password_strong(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('must be at least 8 characters')
        if not any(c.isupper() for c in v):
            raise ValueError('must contain uppercase letter')
        return v

    # Validate multiple fields
    @field_validator('username', 'password')
    @classmethod
    def not_empty(cls, v: str) -> str:
        if not v or not v.strip():
            raise ValueError('must not be empty')
        return v.strip()
python
from pydantic import field_validator, model_validator

class Account(BaseModel):
    username: str
    password: str
    password_confirm: str

    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError('必须为字母数字组合')
        return v

    @field_validator('password')
    @classmethod
    def password_strong(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('长度至少为8个字符')
        if not any(c.isupper() for c in v):
            raise ValueError('必须包含大写字母')
        return v

    # 多字段验证
    @field_validator('username', 'password')
    @classmethod
    def not_empty(cls, v: str) -> str:
        if not v or not v.strip():
            raise ValueError('不能为空')
        return v.strip()

Model Validators

模型验证器

python
from pydantic import model_validator
from typing import Self

class DateRange(BaseModel):
    start_date: datetime
    end_date: datetime

    @model_validator(mode='after')
    def check_dates(self) -> Self:
        if self.end_date < self.start_date:
            raise ValueError('end_date must be after start_date')
        return self

class Order(BaseModel):
    items: list[str]
    total: float
    discount: float = 0

    @model_validator(mode='before')
    @classmethod
    def calculate_total(cls, data: dict) -> dict:
        # Pre-processing before validation
        if isinstance(data, dict) and 'total' not in data:
            data['total'] = len(data.get('items', [])) * 10.0
        return data
python
from pydantic import model_validator
from typing import Self

class DateRange(BaseModel):
    start_date: datetime
    end_date: datetime

    @model_validator(mode='after')
    def check_dates(self) -> Self:
        if self.end_date < self.start_date:
            raise ValueError('结束日期必须晚于开始日期')
        return self

class Order(BaseModel):
    items: list[str]
    total: float
    discount: float = 0

    @model_validator(mode='before')
    @classmethod
    def calculate_total(cls, data: dict) -> dict:
        # 验证前预处理
        if isinstance(data, dict) and 'total' not in data:
            data['total'] = len(data.get('items', [])) * 10.0
        return data

Root Validators (Wrap)

根验证器(包装模式)

python
from pydantic import model_validator, ValidationInfo

class Config(BaseModel):
    env: Literal['dev', 'prod']
    debug: bool = False

    @model_validator(mode='wrap')
    @classmethod
    def validate_config(cls, values: Any, handler, info: ValidationInfo):
        # Call default validation
        result = handler(values)

        # Post-validation logic
        if result.env == 'prod' and result.debug:
            raise ValueError('debug cannot be True in production')

        return result
python
from pydantic import model_validator, ValidationInfo

class Config(BaseModel):
    env: Literal['dev', 'prod']
    debug: bool = False

    @model_validator(mode='wrap')
    @classmethod
    def validate_config(cls, values: Any, handler, info: ValidationInfo):
        # 调用默认验证
        result = handler(values)

        # 验证后逻辑
        if result.env == 'prod' and result.debug:
            raise ValueError('生产环境下debug不能设为True')

        return result

Type Coercion and Strict Mode

类型转换与严格模式

python
from pydantic import BaseModel, ConfigDict, ValidationError
python
from pydantic import BaseModel, ConfigDict, ValidationError

Coercive mode (default)

强制转换模式(默认)

class CoerciveModel(BaseModel): count: int price: float
data = CoerciveModel(count="42", price="19.99") assert data.count == 42 # String → int assert data.price == 19.99 # String → float
class CoerciveModel(BaseModel): count: int price: float
data = CoerciveModel(count="42", price="19.99") assert data.count == 42 # 字符串→整数 assert data.price == 19.99 # 字符串→浮点数

Strict mode

严格模式

class StrictModel(BaseModel): model_config = ConfigDict(strict=True)
count: int
price: float
try: StrictModel(count="42", price="19.99") # Raises ValidationError except ValidationError as e: print("Strict mode: no coercion allowed")
class StrictModel(BaseModel): model_config = ConfigDict(strict=True)
count: int
price: float
try: StrictModel(count="42", price="19.99") # 抛出ValidationError except ValidationError as e: print("严格模式:不允许类型转换")

Per-field strict mode

字段级严格模式

class MixedModel(BaseModel): flexible: int # Allows coercion strict: Annotated[int, Field(strict=True)] # No coercion
MixedModel(flexible="1", strict=2) # OK
class MixedModel(BaseModel): flexible: int # 允许转换 strict: Annotated[int, Field(strict=True)] # 不允许转换
MixedModel(flexible="1", strict=2) # 正常

MixedModel(flexible="1", strict="2") # ValidationError

MixedModel(flexible="1", strict="2") # 验证错误

undefined
undefined

Nested Models and Recursive Types

嵌套模型与递归类型

python
from pydantic import BaseModel
from typing import ForwardRef
python
from pydantic import BaseModel
from typing import ForwardRef

Nested models

嵌套模型

class Address(BaseModel): street: str city: str country: str
class Company(BaseModel): name: str address: Address
company = Company( name="ACME Corp", address={'street': '123 Main St', 'city': 'NYC', 'country': 'USA'} )
class Address(BaseModel): street: str city: str country: str
class Company(BaseModel): name: str address: Address
company = Company( name="ACME Corp", address={'street': '123 Main St', 'city': 'NYC', 'country': 'USA'} )

Recursive types (tree structure)

递归类型(树形结构)

class TreeNode(BaseModel): value: int children: list['TreeNode'] = []
TreeNode.model_rebuild() # Required for forward references
tree = TreeNode( value=1, children=[ TreeNode(value=2, children=[TreeNode(value=4)]), TreeNode(value=3) ] )
class TreeNode(BaseModel): value: int children: list['TreeNode'] = []
TreeNode.model_rebuild() # 前向引用需要调用此方法
tree = TreeNode( value=1, children=[ TreeNode(value=2, children=[TreeNode(value=4)]), TreeNode(value=3) ] )

Self-referencing with ForwardRef

使用ForwardRef实现自引用

class Category(BaseModel): name: str parent: 'Category | None' = None subcategories: list['Category'] = []
Category.model_rebuild()
undefined
class Category(BaseModel): name: str parent: 'Category | None' = None subcategories: list['Category'] = []
Category.model_rebuild()
undefined

Generic Models

泛型模型

python
from pydantic import BaseModel
from typing import Generic, TypeVar

T = TypeVar('T')

class Response(BaseModel, Generic[T]):
    success: bool
    data: T
    message: str = ''

class User(BaseModel):
    id: int
    name: str
python
from pydantic import BaseModel
from typing import Generic, TypeVar

T = TypeVar('T')

class Response(BaseModel, Generic[T]):
    success: bool
    data: T
    message: str = ''

class User(BaseModel):
    id: int
    name: str

Usage with concrete type

使用具体类型

user_response = Response[User]( success=True, data=User(id=1, name='Alice') )
user_response = Response[User]( success=True, data=User(id=1, name='Alice') )

List response

列表响应

list_response = Response[list[User]]( success=True, data=[User(id=1, name='Alice'), User(id=2, name='Bob')] )
list_response = Response[list[User]]( success=True, data=[User(id=1, name='Alice'), User(id=2, name='Bob')] )

Generic repository pattern

泛型仓库模式

class Repository(BaseModel, Generic[T]): items: list[T]
def add(self, item: T) -> None:
    self.items.append(item)
user_repo = RepositoryUser user_repo.add(User(id=1, name='Alice'))
undefined
class Repository(BaseModel, Generic[T]): items: list[T]
def add(self, item: T) -> None:
    self.items.append(item)
user_repo = RepositoryUser user_repo.add(User(id=1, name='Alice'))
undefined

Serialization

序列化

Model Dump

模型转字典

python
from pydantic import BaseModel, Field, field_serializer

class Article(BaseModel):
    title: str
    content: str
    tags: list[str]
    metadata: dict[str, Any] = {}

    # Serialization customization
    @field_serializer('tags')
    def serialize_tags(self, tags: list[str]) -> str:
        return ','.join(tags)

article = Article(
    title='Pydantic Guide',
    content='...',
    tags=['python', 'validation']
)
python
from pydantic import BaseModel, Field, field_serializer

class Article(BaseModel):
    title: str
    content: str
    tags: list[str]
    metadata: dict[str, Any] = {}

    # 自定义序列化
    @field_serializer('tags')
    def serialize_tags(self, tags: list[str]) -> str:
        return ','.join(tags)

article = Article(
    title='Pydantic指南',
    content='...',
    tags=['python', 'validation']
)

Dump to dict

转换为字典

data = article.model_dump()
data = article.model_dump()

{'title': 'Pydantic Guide', 'tags': 'python,validation', ...}

{'title': 'Pydantic指南', 'tags': 'python,validation', ...}

Exclude fields

排除指定字段

data = article.model_dump(exclude={'metadata'})
data = article.model_dump(exclude={'metadata'})

Include only specific fields

仅包含指定字段

data = article.model_dump(include={'title', 'tags'})
data = article.model_dump(include={'title', 'tags'})

Exclude unset fields

排除未设置的字段

article2 = Article(title='Test', content='...', tags=[]) data = article2.model_dump(exclude_unset=True) # metadata excluded
article2 = Article(title='测试', content='...', tags=[]) data = article2.model_dump(exclude_unset=True) # metadata被排除

By alias

使用别名

class AliasModel(BaseModel): internal_name: str = Field(alias='externalName')
model = AliasModel(externalName='value') model.model_dump(by_alias=True) # {'externalName': 'value'}
undefined
class AliasModel(BaseModel): internal_name: str = Field(alias='externalName')
model = AliasModel(externalName='value') model.model_dump(by_alias=True) # {'externalName': 'value'}
undefined

JSON Serialization

JSON序列化

python
from datetime import datetime
from pydantic import BaseModel, field_serializer

class Event(BaseModel):
    name: str
    timestamp: datetime

    @field_serializer('timestamp')
    def serialize_dt(self, dt: datetime) -> str:
        return dt.isoformat()

event = Event(name='Deploy', timestamp=datetime.now())
python
from datetime import datetime
from pydantic import BaseModel, field_serializer

class Event(BaseModel):
    name: str
    timestamp: datetime

    @field_serializer('timestamp')
    def serialize_dt(self, dt: datetime) -> str:
        return dt.isoformat()

event = Event(name='部署', timestamp=datetime.now())

Dump to JSON string

转换为JSON字符串

json_str = event.model_dump_json()
json_str = event.model_dump_json()

'{"name":"Deploy","timestamp":"2025-11-30T..."}'

'{"name":"部署","timestamp":"2025-11-30T..."}'

Pretty print

格式化输出

json_str = event.model_dump_json(indent=2)
json_str = event.model_dump_json(indent=2)

Parse from JSON

从JSON解析

event2 = Event.model_validate_json(json_str)
undefined
event2 = Event.model_validate_json(json_str)
undefined

Custom Serializers

自定义序列化器

python
from pydantic import model_serializer

class User(BaseModel):
    id: int
    username: str
    password: SecretStr

    @model_serializer
    def ser_model(self) -> dict[str, Any]:
        return {
            'id': self.id,
            'username': self.username,
            # Never serialize password
        }

user = User(id=1, username='alice', password='secret123')
assert 'password' not in user.model_dump()
python
from pydantic import model_serializer

class User(BaseModel):
    id: int
    username: str
    password: SecretStr

    @model_serializer
    def ser_model(self) -> dict[str, Any]:
        return {
            'id': self.id,
            'username': self.username,
            # 密码永不序列化
        }

user = User(id=1, username='alice', password='secret123')
assert 'password' not in user.model_dump()

Settings Management

配置管理

BaseSettings

BaseSettings

python
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field

class AppSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file='.env',
        env_file_encoding='utf-8',
        env_prefix='APP_',
        case_sensitive=False
    )

    # Environment variables
    database_url: str
    redis_url: str = 'redis://localhost:6379'
    secret_key: SecretStr
    debug: bool = False

    # Nested settings
    class SMTPSettings(BaseModel):
        host: str
        port: int = 587
        username: str
        password: SecretStr

    smtp: SMTPSettings
python
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field

class AppSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file='.env',
        env_file_encoding='utf-8',
        env_prefix='APP_',
        case_sensitive=False
    )

    # 环境变量
    database_url: str
    redis_url: str = 'redis://localhost:6379'
    secret_key: SecretStr
    debug: bool = False

    # 嵌套配置
    class SMTPSettings(BaseModel):
        host: str
        port: int = 587
        username: str
        password: SecretStr

    smtp: SMTPSettings

Reads from environment variables:

从环境变量读取:

APP_DATABASE_URL, APP_REDIS_URL, APP_SECRET_KEY, APP_DEBUG

APP_DATABASE_URL, APP_REDIS_URL, APP_SECRET_KEY, APP_DEBUG

APP_SMTP__HOST, APP_SMTP__PORT, etc.

APP_SMTP__HOST, APP_SMTP__PORT, 等

settings = AppSettings()
undefined
settings = AppSettings()
undefined

Multi-Environment Settings

多环境配置

python
from functools import lru_cache

class Settings(BaseSettings):
    environment: Literal['dev', 'staging', 'prod'] = 'dev'
    database_url: str
    api_key: SecretStr

    model_config = SettingsConfigDict(
        env_file='.env',
        extra='ignore'
    )

    @property
    def is_production(self) -> bool:
        return self.environment == 'prod'

@lru_cache
def get_settings() -> Settings:
    return Settings()
python
from functools import lru_cache

class Settings(BaseSettings):
    environment: Literal['dev', 'staging', 'prod'] = 'dev'
    database_url: str
    api_key: SecretStr

    model_config = SettingsConfigDict(
        env_file='.env',
        extra='ignore'
    )

    @property
    def is_production(self) -> bool:
        return self.environment == 'prod'

@lru_cache
def get_settings() -> Settings:
    return Settings()

Usage in FastAPI

在FastAPI中使用

from fastapi import Depends
@app.get('/config') def get_config(settings: Settings = Depends(get_settings)): return {'env': settings.environment}
undefined
from fastapi import Depends
@app.get('/config') def get_config(settings: Settings = Depends(get_settings)): return {'env': settings.environment}
undefined

FastAPI Integration

FastAPI集成

Request/Response Models

请求/响应模型

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=50)
    email: EmailStr
    password: str = Field(min_length=8)

class UserResponse(BaseModel):
    id: int
    username: str
    email: EmailStr

    model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse)
def create_user(user: UserCreate):
    # FastAPI auto-validates request body
    # Returns only fields in UserResponse (password excluded)
    return UserResponse(
        id=1,
        username=user.username,
        email=user.email
    )
python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=50)
    email: EmailStr
    password: str = Field(min_length=8)

class UserResponse(BaseModel):
    id: int
    username: str
    email: EmailStr

    model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse)
def create_user(user: UserCreate):
    # FastAPI自动验证请求体
    # 仅返回UserResponse中定义的字段(排除密码)
    return UserResponse(
        id=1,
        username=user.username,
        email=user.email
    )

Query Parameters

查询参数

python
from pydantic import BaseModel, Field
from fastapi import Query

class PaginationParams(BaseModel):
    skip: int = Field(0, ge=0)
    limit: int = Field(10, ge=1, le=100)

class SearchParams(BaseModel):
    q: str = Field(..., min_length=1)
    category: str | None = None
    sort_by: Literal['date', 'relevance'] = 'relevance'

@app.get('/search')
def search(params: SearchParams = Query()):
    return {'query': params.q, 'sort': params.sort_by}
python
from pydantic import BaseModel, Field
from fastapi import Query

class PaginationParams(BaseModel):
    skip: int = Field(0, ge=0)
    limit: int = Field(10, ge=1, le=100)

class SearchParams(BaseModel):
    q: str = Field(..., min_length=1)
    category: str | None = None
    sort_by: Literal['date', 'relevance'] = 'relevance'

@app.get('/search')
def search(params: SearchParams = Query()):
    return {'query': params.q, 'sort': params.sort_by}

Response Model Customization

响应模型自定义

python
class DetailedUser(BaseModel):
    id: int
    username: str
    email: EmailStr
    created_at: datetime
    last_login: datetime | None

@app.get('/users/{user_id}', response_model=DetailedUser)
def get_user(user_id: int, include_dates: bool = False):
    user = DetailedUser(
        id=user_id,
        username='alice',
        email='alice@example.com',
        created_at=datetime.now(),
        last_login=None
    )

    if not include_dates:
        return user.model_dump(exclude={'created_at', 'last_login'})
    return user
python
class DetailedUser(BaseModel):
    id: int
    username: str
    email: EmailStr
    created_at: datetime
    last_login: datetime | None

@app.get('/users/{user_id}', response_model=DetailedUser)
def get_user(user_id: int, include_dates: bool = False):
    user = DetailedUser(
        id=user_id,
        username='alice',
        email='alice@example.com',
        created_at=datetime.now(),
        last_login=None
    )

    if not include_dates:
        return user.model_dump(exclude={'created_at', 'last_login'})
    return user

SQLAlchemy Integration

SQLAlchemy集成

ORM Models with Pydantic

ORM模型与Pydantic

python
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase):
    pass
python
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase):
    pass

SQLAlchemy ORM model

SQLAlchemy ORM模型

class UserDB(Base): tablename = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True)
email = Column(String(100))
created_at = Column(DateTime, default=datetime.utcnow)
class UserDB(Base): tablename = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True)
email = Column(String(100))
created_at = Column(DateTime, default=datetime.utcnow)

Pydantic model for validation

Pydantic验证模型

class UserSchema(BaseModel): model_config = ConfigDict(from_attributes=True)
id: int
username: str
email: EmailStr
created_at: datetime
class UserSchema(BaseModel): model_config = ConfigDict(from_attributes=True)
id: int
username: str
email: EmailStr
created_at: datetime

Usage

使用示例

from sqlalchemy.orm import Session
def get_user(db: Session, user_id: int) -> UserSchema: user = db.query(UserDB).filter(UserDB.id == user_id).first() return UserSchema.model_validate(user) # ORM → Pydantic
undefined
from sqlalchemy.orm import Session
def get_user(db: Session, user_id: int) -> UserSchema: user = db.query(UserDB).filter(UserDB.id == user_id).first() return UserSchema.model_validate(user) # ORM → Pydantic
undefined

Hybrid Approach

混合方案

python
from pydantic import BaseModel

class UserBase(BaseModel):
    username: str
    email: EmailStr

class UserCreate(UserBase):
    password: str

class UserUpdate(BaseModel):
    username: str | None = None
    email: EmailStr | None = None
    password: str | None = None

class UserInDB(UserBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    created_at: datetime
    password_hash: str
python
from pydantic import BaseModel

class UserBase(BaseModel):
    username: str
    email: EmailStr

class UserCreate(UserBase):
    password: str

class UserUpdate(BaseModel):
    username: str | None = None
    email: EmailStr | None = None
    password: str | None = None

class UserInDB(UserBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    created_at: datetime
    password_hash: str

CRUD operations

CRUD操作

def create_user(db: Session, user: UserCreate) -> UserInDB: db_user = UserDB( username=user.username, email=user.email, password_hash=hash_password(user.password) ) db.add(db_user) db.commit() db.refresh(db_user) return UserInDB.model_validate(db_user)
undefined
def create_user(db: Session, user: UserCreate) -> UserInDB: db_user = UserDB( username=user.username, email=user.email, password_hash=hash_password(user.password) ) db.add(db_user) db.commit() db.refresh(db_user) return UserInDB.model_validate(db_user)
undefined

Django Integration

Django集成

Django Model Validation

Django模型验证

python
from django.db import models
from pydantic import BaseModel, field_validator
python
from django.db import models
from pydantic import BaseModel, field_validator

Django model

Django模型

class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() published = models.BooleanField(default=False)
class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() published = models.BooleanField(default=False)

Pydantic schema

Pydantic schema

class ArticleSchema(BaseModel): model_config = ConfigDict(from_attributes=True)
title: str = Field(max_length=200)
content: str
published: bool = False

@field_validator('content')
@classmethod
def validate_content(cls, v: str) -> str:
    if len(v) < 100:
        raise ValueError('Content too short')
    return v
class ArticleSchema(BaseModel): model_config = ConfigDict(from_attributes=True)
title: str = Field(max_length=200)
content: str
published: bool = False

@field_validator('content')
@classmethod
def validate_content(cls, v: str) -> str:
    if len(v) < 100:
        raise ValueError('内容过短')
    return v

Usage in Django views

在Django视图中使用

from django.http import JsonResponse from django.views.decorators.http import require_http_methods
@require_http_methods(['POST']) def create_article(request): try: data = ArticleSchema.model_validate_json(request.body) article = Article.objects.create(**data.model_dump()) return JsonResponse({'id': article.id}) except ValidationError as e: return JsonResponse({'errors': e.errors()}, status=400)
undefined
from django.http import JsonResponse from django.views.decorators.http import require_http_methods
@require_http_methods(['POST']) def create_article(request): try: data = ArticleSchema.model_validate_json(request.body) article = Article.objects.create(**data.model_dump()) return JsonResponse({'id': article.id}) except ValidationError as e: return JsonResponse({'errors': e.errors()}, status=400)
undefined

Computed Fields

计算字段

python
from pydantic import computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

    @computed_field
    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

rect = Rectangle(width=10, height=5)
assert rect.area == 50
assert rect.perimeter == 30
python
from pydantic import computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

    @computed_field
    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

rect = Rectangle(width=10, height=5)
assert rect.area == 50
assert rect.perimeter == 30

Computed fields in serialization

计算字段会被序列化

data = rect.model_dump()
data = rect.model_dump()

{'width': 10.0, 'height': 5.0, 'area': 50.0, 'perimeter': 30.0}

{'width': 10.0, 'height': 5.0, 'area': 50.0, 'perimeter': 30.0}

undefined
undefined

Custom Errors

自定义错误

python
from pydantic import BaseModel, field_validator, ValidationError
from pydantic_core import PydanticCustomError

class StrictUser(BaseModel):
    username: str
    age: int

    @field_validator('username')
    @classmethod
    def validate_username(cls, v: str) -> str:
        if len(v) < 3:
            raise PydanticCustomError(
                'username_too_short',
                'Username must be at least 3 characters',
                {'min_length': 3, 'actual_length': len(v)}
            )
        return v

    @field_validator('age')
    @classmethod
    def validate_age(cls, v: int) -> int:
        if v < 18:
            raise PydanticCustomError(
                'underage',
                'User must be at least 18 years old',
                {'age': v, 'minimum_age': 18}
            )
        return v
python
from pydantic import BaseModel, field_validator, ValidationError
from pydantic_core import PydanticCustomError

class StrictUser(BaseModel):
    username: str
    age: int

    @field_validator('username')
    @classmethod
    def validate_username(cls, v: str) -> str:
        if len(v) < 3:
            raise PydanticCustomError(
                'username_too_short',
                '用户名长度至少为3个字符',
                {'min_length': 3, 'actual_length': len(v)}
            )
        return v

    @field_validator('age')
    @classmethod
    def validate_age(cls, v: int) -> int:
        if v < 18:
            raise PydanticCustomError(
                'underage',
                '用户必须年满18岁',
                {'age': v, 'minimum_age': 18}
            )
        return v

Custom error handling

自定义错误处理

try: StrictUser(username='ab', age=16) except ValidationError as e: for error in e.errors(): print(f"{error['type']}: {error['msg']}") print(f"Context: {error.get('ctx')}")
undefined
try: StrictUser(username='ab', age=16) except ValidationError as e: for error in e.errors(): print(f"{error['type']}: {error['msg']}") print(f"上下文: {error.get('ctx')}")
undefined

Performance Optimization

性能优化

V2 Rust Core Benefits

V2 Rust核心优势

python
undefined
python
undefined

Pydantic v2 uses pydantic-core (Rust) for:

Pydantic v2 使用pydantic-core(Rust)实现:

- 5-50x faster validation

- 5-50倍更快的验证速度

- Lower memory usage

- 更低的内存占用

- Better error messages

- 更友好的错误提示

- Improved JSON parsing

- 改进的JSON解析

import timeit from pydantic import BaseModel
class Data(BaseModel): values: list[int] names: list[str] metadata: dict[str, Any]
import timeit from pydantic import BaseModel
class Data(BaseModel): values: list[int] names: list[str] metadata: dict[str, Any]

Benchmark

基准测试

data_dict = { 'values': list(range(1000)), 'names': ['item'] * 1000, 'metadata': {'key': 'value'} }
def validate(): Data.model_validate(data_dict)
time_taken = timeit.timeit(validate, number=10000) print(f"10000 validations: {time_taken:.2f}s")
undefined
data_dict = { 'values': list(range(1000)), 'names': ['item'] * 1000, 'metadata': {'key': 'value'} }
def validate(): Data.model_validate(data_dict)
time_taken = timeit.timeit(validate, number=10000) print(f"10000次验证耗时: {time_taken:.2f}秒")
undefined

Optimization Techniques

优化技巧

python
from pydantic import BaseModel, ConfigDict

class OptimizedModel(BaseModel):
    model_config = ConfigDict(
        # Validate assignment only when needed
        validate_assignment=False,

        # Disable validation for internal use
        validate_default=False,

        # Use slots for memory efficiency
        # (Not available in Pydantic v2 BaseModel directly)
    )

    data: list[int]
python
from pydantic import BaseModel, ConfigDict

class OptimizedModel(BaseModel):
    model_config = ConfigDict(
        # 仅在需要时验证赋值
        validate_assignment=False,

        # 禁用默认值验证
        validate_default=False,

        # 使用slots提升内存效率
        # (Pydantic v2 BaseModel暂不直接支持)
    )

    data: list[int]

Reuse validators

复用验证器

from functools import lru_cache
@lru_cache(maxsize=128) def get_validator(model_class): return model_class.model_validate
from functools import lru_cache
@lru_cache(maxsize=128) def get_validator(model_class): return model_class.model_validate

Bulk validation

批量验证

def validate_bulk(items: list[dict]) -> list[Data]: validator = get_validator(Data) return [validator(item) for item in items]
undefined
def validate_bulk(items: list[dict]) -> list[Data]: validator = get_validator(Data) return [validator(item) for item in items]
undefined

JSON Schema Generation

JSON Schema生成

python
from pydantic import BaseModel, Field

class Product(BaseModel):
    """Product model for catalog"""

    id: int = Field(description="Unique product identifier")
    name: str = Field(description="Product name", examples=["Widget"])
    price: float = Field(gt=0, description="Price in USD")
    tags: list[str] = Field(default=[], description="Product tags")
python
from pydantic import BaseModel, Field

class Product(BaseModel):
    """产品目录模型"""

    id: int = Field(description="唯一产品ID")
    name: str = Field(description="产品名称", examples=["小部件"])
    price: float = Field(gt=0, description="美元价格")
    tags: list[str] = Field(default=[], description="产品标签")

Generate JSON Schema

生成JSON Schema

schema = Product.model_json_schema() print(json.dumps(schema, indent=2))
schema = Product.model_json_schema() print(json.dumps(schema, indent=2))

{

{

"title": "Product",

"title": "Product",

"description": "Product model for catalog",

"description": "产品目录模型",

"type": "object",

"type": "object",

"properties": {

"properties": {

"id": {"type": "integer", "description": "Unique product identifier"},

"id": {"type": "integer", "description": "唯一产品ID"},

"name": {"type": "string", "description": "Product name"},

"name": {"type": "string", "description": "产品名称"},

...

...

},

},

"required": ["id", "name", "price"]

"required": ["id", "name", "price"]

}

}

OpenAPI compatible

兼容OpenAPI

from fastapi import FastAPI
app = FastAPI()
@app.post('/products') def create_product(product: Product): return product
from fastapi import FastAPI
app = FastAPI()
@app.post('/products') def create_product(product: Product): return product

FastAPI auto-generates OpenAPI schema from Pydantic models

FastAPI自动从Pydantic模型生成OpenAPI Schema

undefined
undefined

Dataclass Integration

数据类集成

python
from pydantic.dataclasses import dataclass
from pydantic import Field

@dataclass
class User:
    id: int
    name: str = Field(min_length=1)
    email: str = Field(pattern=r'.+@.+\..+')
python
from pydantic.dataclasses import dataclass
from pydantic import Field

@dataclass
class User:
    id: int
    name: str = Field(min_length=1)
    email: str = Field(pattern=r'.+@.+\..+')

Works like Pydantic BaseModel with validation

具备Pydantic BaseModel的验证能力

user = User(id=1, name='Alice', email='alice@example.com')
user = User(id=1, name='Alice', email='alice@example.com')

Validation on construction

实例化时验证

try: User(id=2, name='', email='invalid') except ValidationError as e: print(e.errors())
try: User(id=2, name='', email='invalid') except ValidationError as e: print(e.errors())

Convert to Pydantic BaseModel

转换为Pydantic BaseModel

from pydantic import BaseModel
class UserModel(BaseModel): model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
user_model = UserModel.model_validate(user)
undefined
from pydantic import BaseModel
class UserModel(BaseModel): model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
user_model = UserModel.model_validate(user)
undefined

Testing Strategies

测试策略

Unit Testing Models

模型单元测试

python
import pytest
from pydantic import ValidationError

def test_user_validation():
    # Valid data
    user = User(id=1, name='Alice', email='alice@example.com')
    assert user.name == 'Alice'

    # Invalid data
    with pytest.raises(ValidationError) as exc_info:
        User(id='invalid', name='Bob', email='bob@example.com')

    errors = exc_info.value.errors()
    assert errors[0]['type'] == 'int_parsing'

def test_user_serialization():
    user = User(id=1, name='Alice', email='alice@example.com')
    data = user.model_dump()

    assert data == {
        'id': 1,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

def test_nested_validation():
    company = Company(
        name='ACME',
        address={'street': '123 Main', 'city': 'NYC', 'country': 'USA'}
    )
    assert company.address.city == 'NYC'
python
import pytest
from pydantic import ValidationError

def test_user_validation():
    # 有效数据
    user = User(id=1, name='Alice', email='alice@example.com')
    assert user.name == 'Alice'

    # 无效数据
    with pytest.raises(ValidationError) as exc_info:
        User(id='invalid', name='Bob', email='bob@example.com')

    errors = exc_info.value.errors()
    assert errors[0]['type'] == 'int_parsing'

def test_user_serialization():
    user = User(id=1, name='Alice', email='alice@example.com')
    data = user.model_dump()

    assert data == {
        'id': 1,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

def test_nested_validation():
    company = Company(
        name='ACME',
        address={'street': '123 Main', 'city': 'NYC', 'country': 'USA'}
    )
    assert company.address.city == 'NYC'

Testing with Fixtures

测试夹具使用

python
@pytest.fixture
def sample_user_data():
    return {
        'id': 1,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

@pytest.fixture
def sample_user(sample_user_data):
    return User(**sample_user_data)

def test_with_fixtures(sample_user):
    assert sample_user.name == 'Alice'

def test_invalid_email(sample_user_data):
    sample_user_data['email'] = 'invalid'
    with pytest.raises(ValidationError):
        User(**sample_user_data)
python
@pytest.fixture
def sample_user_data():
    return {
        'id': 1,
        'name': 'Alice',
        'email': 'alice@example.com'
    }

@pytest.fixture
def sample_user(sample_user_data):
    return User(**sample_user_data)

def test_with_fixtures(sample_user):
    assert sample_user.name == 'Alice'

def test_invalid_email(sample_user_data):
    sample_user_data['email'] = 'invalid'
    with pytest.raises(ValidationError):
        User(**sample_user_data)

Property-Based Testing

属性化测试

python
from hypothesis import given, strategies as st

@given(
    id=st.integers(min_value=1),
    name=st.text(min_size=1, max_size=100),
    email=st.emails()
)
def test_user_always_valid(id, name, email):
    user = User(id=id, name=name, email=email)
    assert user.id == id
    assert user.name == name
    assert user.email == email
python
from hypothesis import given, strategies as st

@given(
    id=st.integers(min_value=1),
    name=st.text(min_size=1, max_size=100),
    email=st.emails()
)
def test_user_always_valid(id, name, email):
    user = User(id=id, name=name, email=email)
    assert user.id == id
    assert user.name == name
    assert user.email == email

Migration Guide (v1 → v2)

迁移指南(v1 → v2)

Key Changes

主要变更

python
undefined
python
undefined

v1

v1

from pydantic import BaseModel
class OldModel(BaseModel): class Config: validate_assignment = True arbitrary_types_allowed = True
# Validators
@validator('field')
def validate_field(cls, v):
    return v

@root_validator
def validate_model(cls, values):
    return values

# Serialization
data = model.dict()
json_str = model.json()

# Parsing
model = OldModel.parse_obj(data)
model = OldModel.parse_raw(json_str)
from pydantic import BaseModel
class OldModel(BaseModel): class Config: validate_assignment = True arbitrary_types_allowed = True
# 验证器
@validator('field')
def validate_field(cls, v):
    return v

@root_validator
def validate_model(cls, values):
    return values

# 序列化
data = model.dict()
json_str = model.json()

# 解析
model = OldModel.parse_obj(data)
model = OldModel.parse_raw(json_str)

v2

v2

from pydantic import BaseModel, ConfigDict, field_validator, model_validator
class NewModel(BaseModel): model_config = ConfigDict( validate_assignment=True, arbitrary_types_allowed=True )
# Field validators
@field_validator('field')
@classmethod
def validate_field(cls, v):
    return v

# Model validators
@model_validator(mode='after')
def validate_model(self):
    return self

# Serialization
data = model.model_dump()
json_str = model.model_dump_json()

# Parsing
model = NewModel.model_validate(data)
model = NewModel.model_validate_json(json_str)
undefined
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
class NewModel(BaseModel): model_config = ConfigDict( validate_assignment=True, arbitrary_types_allowed=True )
# 字段验证器
@field_validator('field')
@classmethod
def validate_field(cls, v):
    return v

# 模型验证器
@model_validator(mode='after')
def validate_model(self):
    return self

# 序列化
data = model.model_dump()
json_str = model.model_dump_json()

# 解析
model = NewModel.model_validate(data)
model = NewModel.model_validate_json(json_str)
undefined

Migration Checklist

迁移检查清单

  • Replace
    class Config
    with
    model_config = ConfigDict()
  • Update
    .dict()
    .model_dump()
  • Update
    .json()
    .model_dump_json()
  • Update
    .parse_obj()
    .model_validate()
  • Update
    .parse_raw()
    .model_validate_json()
  • Update
    @validator
    @field_validator
    with
    @classmethod
  • Update
    @root_validator
    @model_validator(mode='after')
  • Review
    json_encoders
    → use
    @field_serializer
  • Test strict mode behavior changes
  • Update custom types to use
    __get_pydantic_core_schema__
  • class Config
    替换为
    model_config = ConfigDict()
  • 更新
    .dict()
    .model_dump()
  • 更新
    .json()
    .model_dump_json()
  • 更新
    .parse_obj()
    .model_validate()
  • 更新
    .parse_raw()
    .model_validate_json()
  • 更新
    @validator
    → 带
    @classmethod
    @field_validator
  • 更新
    @root_validator
    @model_validator(mode='after')
  • 替换
    json_encoders
    → 使用
    @field_serializer
  • 测试严格模式行为变更
  • 更新自定义类型以使用
    __get_pydantic_core_schema__

Best Practices

最佳实践

Model Organization

模型组织

python
undefined
python
undefined

Separate schemas by use case

按使用场景拆分Schema

class UserBase(BaseModel): """Shared fields""" username: str email: EmailStr
class UserCreate(UserBase): """API request for creating user""" password: str
class UserUpdate(BaseModel): """API request for updating user (all optional)""" username: str | None = None email: EmailStr | None = None password: str | None = None
class UserInDB(UserBase): """Database representation""" model_config = ConfigDict(from_attributes=True)
id: int
password_hash: str
created_at: datetime
class UserResponse(UserBase): """API response (excludes sensitive data)""" id: int created_at: datetime
undefined
class UserBase(BaseModel): """共享字段""" username: str email: EmailStr
class UserCreate(UserBase): """创建用户的API请求模型""" password: str
class UserUpdate(BaseModel): """更新用户的API请求模型(所有字段可选)""" username: str | None = None email: EmailStr | None = None password: str | None = None
class UserInDB(UserBase): """数据库表示模型""" model_config = ConfigDict(from_attributes=True)
id: int
password_hash: str
created_at: datetime
class UserResponse(UserBase): """API响应模型(排除敏感数据)""" id: int created_at: datetime
undefined

Validation Best Practices

验证最佳实践

python
undefined
python
undefined

Use Field for constraints, not validators

使用Field定义约束,而非验证器

class Good(BaseModel): age: int = Field(ge=0, le=150) email: EmailStr
class Bad(BaseModel): age: int email: str
@field_validator('age')
@classmethod
def validate_age(cls, v):
    if v < 0 or v > 150:
        raise ValueError('invalid age')
    return v
class Good(BaseModel): age: int = Field(ge=0, le=150) email: EmailStr
class Bad(BaseModel): age: int email: str
@field_validator('age')
@classmethod
def validate_age(cls, v):
    if v < 0 or v > 150:
        raise ValueError('无效年龄')
    return v

Prefer composition over inheritance

优先组合而非继承

class TimestampMixin(BaseModel): created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow)
class User(TimestampMixin): username: str email: EmailStr
undefined
class TimestampMixin(BaseModel): created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow)
class User(TimestampMixin): username: str email: EmailStr
undefined

Error Handling

错误处理

python
from pydantic import ValidationError

def safe_validate(data: dict) -> User | None:
    try:
        return User.model_validate(data)
    except ValidationError as e:
        # Log validation errors
        logger.error(f"Validation failed: {e.errors()}")
        return None

def validate_with_details(data: dict):
    try:
        return User.model_validate(data)
    except ValidationError as e:
        # Return user-friendly errors
        return {
            'success': False,
            'errors': [
                {
                    'field': '.'.join(str(loc) for loc in err['loc']),
                    'message': err['msg'],
                    'type': err['type']
                }
                for err in e.errors()
            ]
        }
python
from pydantic import ValidationError

def safe_validate(data: dict) -> User | None:
    try:
        return User.model_validate(data)
    except ValidationError as e:
        # 记录验证错误
        logger.error(f"验证失败: {e.errors()}")
        return None

def validate_with_details(data: dict):
    try:
        return User.model_validate(data)
    except ValidationError as e:
        # 返回用户友好的错误信息
        return {
            'success': False,
            'errors': [
                {
                    'field': '.'.join(str(loc) for loc in err['loc']),
                    'message': err['msg'],
                    'type': err['type']
                }
                for err in e.errors()
            ]
        }

Common Patterns

常见模式

API Response Wrapper

API响应包装器

python
from typing import Generic, TypeVar

T = TypeVar('T')

class APIResponse(BaseModel, Generic[T]):
    success: bool
    data: T | None = None
    error: str | None = None
    metadata: dict[str, Any] = {}
python
from typing import Generic, TypeVar

T = TypeVar('T')

class APIResponse(BaseModel, Generic[T]):
    success: bool
    data: T | None = None
    error: str | None = None
    metadata: dict[str, Any] = {}

Usage

使用示例

user_response = APIResponse[User]( success=True, data=User(id=1, name='Alice', email='alice@example.com') )
error_response = APIResponse[User]( success=False, error='User not found' )
undefined
user_response = APIResponse[User]( success=True, data=User(id=1, name='Alice', email='alice@example.com') )
error_response = APIResponse[User]( success=False, error='用户不存在' )
undefined

Pagination

分页

python
class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    page_size: int

    @computed_field
    @property
    def total_pages(self) -> int:
        return (self.total + self.page_size - 1) // self.page_size

users = PaginatedResponse[User](
    items=[...],
    total=100,
    page=1,
    page_size=10
)
assert users.total_pages == 10
python
class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    page_size: int

    @computed_field
    @property
    def total_pages(self) -> int:
        return (self.total + self.page_size - 1) // self.page_size

users = PaginatedResponse[User](
    items=[...],
    total=100,
    page=1,
    page_size=10
)
assert users.total_pages == 10

Audit Fields

审计字段

python
class AuditMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)
    created_by: int | None = None
    updated_by: int | None = None

class Document(AuditMixin):
    title: str
    content: str

    @model_validator(mode='before')
    @classmethod
    def update_timestamp(cls, data: dict) -> dict:
        if isinstance(data, dict):
            data['updated_at'] = datetime.utcnow()
        return data
python
class AuditMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)
    created_by: int | None = None
    updated_by: int | None = None

class Document(AuditMixin):
    title: str
    content: str

    @model_validator(mode='before')
    @classmethod
    def update_timestamp(cls, data: dict) -> dict:
        if isinstance(data, dict):
            data['updated_at'] = datetime.utcnow()
        return data

Related Skills

相关技能

When using Pydantic, consider these complementary skills:
  • fastapi-local-dev: FastAPI development server patterns with Pydantic integration
  • sqlalchemy: SQLAlchemy ORM patterns for database models with Pydantic validation
  • django: Django framework integration with Pydantic schemas
  • pytest: Testing strategies for Pydantic models and validation
使用Pydantic时,可搭配以下互补技能:
  • fastapi-local-dev: 带Pydantic集成的FastAPI开发服务器模式
  • sqlalchemy: 带Pydantic验证的SQLAlchemy ORM模型模式
  • django: 带Pydantic Schema的Django框架集成
  • pytest: Pydantic模型与验证的测试策略

Quick FastAPI Integration Reference (Inlined for Standalone Use)

快速FastAPI集成参考(独立使用)

python
undefined
python
undefined

FastAPI with Pydantic (basic pattern)

FastAPI与Pydantic(基础模式)

from fastapi import FastAPI, HTTPException from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserCreate(BaseModel): username: str email: EmailStr password: str
class UserResponse(BaseModel): id: int username: str email: EmailStr
model_config = ConfigDict(from_attributes=True)
@app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): # FastAPI auto-validates using Pydantic # response_model filters out password return UserResponse(id=1, username=user.username, email=user.email)
undefined
from fastapi import FastAPI, HTTPException from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserCreate(BaseModel): username: str email: EmailStr password: str
class UserResponse(BaseModel): id: int username: str email: EmailStr
model_config = ConfigDict(from_attributes=True)
@app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): # FastAPI通过Pydantic自动验证 # response_model过滤掉密码字段 return UserResponse(id=1, username=user.username, email=user.email)
undefined

Quick SQLAlchemy Integration Reference (Inlined for Standalone Use)

快速SQLAlchemy集成参考(独立使用)

python
undefined
python
undefined

SQLAlchemy 2.0 with Pydantic validation

SQLAlchemy 2.0与Pydantic验证

from sqlalchemy import Column, Integer, String from sqlalchemy.orm import DeclarativeBase from pydantic import BaseModel, ConfigDict
class Base(DeclarativeBase): pass
class UserDB(Base): tablename = 'users' id = Column(Integer, primary_key=True) username = Column(String(50)) email = Column(String(100))
class UserSchema(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str email: str
from sqlalchemy import Column, Integer, String from sqlalchemy.orm import DeclarativeBase from pydantic import BaseModel, ConfigDict
class Base(DeclarativeBase): pass
class UserDB(Base): tablename = 'users' id = Column(Integer, primary_key=True) username = Column(String(50)) email = Column(String(100))
class UserSchema(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str email: str

Convert ORM to Pydantic

ORM转Pydantic

user_orm = db.query(UserDB).first() user_validated = UserSchema.model_validate(user_orm)
undefined
user_orm = db.query(UserDB).first() user_validated = UserSchema.model_validate(user_orm)
undefined

Quick Pytest Testing Reference (Inlined for Standalone Use)

快速Pytest测试参考(独立使用)

python
undefined
python
undefined

Testing Pydantic models with pytest

使用pytest测试Pydantic模型

import pytest from pydantic import ValidationError
def test_user_validation(): user = User(id=1, name='Alice', email='alice@example.com') assert user.name == 'Alice'
def test_validation_error(): with pytest.raises(ValidationError) as exc_info: User(id='invalid', name='Bob', email='bob@example.com') errors = exc_info.value.errors() assert errors[0]['type'] == 'int_parsing'
@pytest.fixture def sample_user(): return User(id=1, name='Alice', email='alice@example.com')

[Full integration patterns available in respective skills if deployed together]
import pytest from pydantic import ValidationError
def test_user_validation(): user = User(id=1, name='Alice', email='alice@example.com') assert user.name == 'Alice'
def test_validation_error(): with pytest.raises(ValidationError) as exc_info: User(id='invalid', name='Bob', email='bob@example.com') errors = exc_info.value.errors() assert errors[0]['type'] == 'int_parsing'
@pytest.fixture def sample_user(): return User(id=1, name='Alice', email='alice@example.com')

[完整集成模式需与对应技能一同部署]

Additional Resources

额外资源