Skip to content

30|Pydantic 高级校验与嵌套模型

基础类型注解能约束数据的形状,但生产环境需要更精细的校验:邮箱格式、字符串长度、数字范围、枚举值、自定义业务规则。Pydantic 的 Field 和校验器(validator)提供了这些能力。

一、Field 约束

python
from pydantic import BaseModel, Field

class ServerCreate(BaseModel):
    hostname: str = Field(..., min_length=1, max_length=64)
    port: int = Field(..., ge=1, le=65535)
    description: str = Field(default="", max_length=500)
    tags: list[str] = Field(default_factory=list, max_length=10)
参数作用适用类型
min_length / max_length字符串长度限制str
ge / le / gt / lt数值范围intfloat
regex正则匹配str
default默认值所有类型
default_factory动态默认值(如空列表)listdictset

字符串正则校验

python
class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=20, regex=r"^[a-zA-Z0-9_]+$")
    email: str = Field(..., regex=r"^[\w\.-]+@[\w\.-]+\.\w+$")

regex 用正则表达式约束字符串格式。上面的 username 只允许字母、数字和下划线。

枚举约束

python
from enum import Enum

class Status(str, Enum):
    draft = "draft"
    published = "published"
    archived = "archived"

class ArticleCreate(BaseModel):
    title: str
    status: Status = Status.draft

status 只能是 draftpublishedarchived 三者之一,传入其他值返回 422。

二、自定义校验器

Field 不够用时,用 @validator 写自定义逻辑:

python
from pydantic import BaseModel, validator

class ServerCreate(BaseModel):
    hostname: str
    ip: str

    @validator("hostname")
    def hostname_must_not_be_empty(cls, v):
        if not v or not v.strip():
            raise ValueError("主机名不能为空")
        return v.strip()

    @validator("ip")
    def ip_must_be_valid(cls, v):
        parts = v.split(".")
        if len(parts) != 4:
            raise ValueError("IP 格式错误")
        for part in parts:
            if not part.isdigit() or not 0 <= int(part) <= 255:
                raise ValueError("IP 格式错误")
        return v

校验器接收类(cls)和字段值(v),返回校验后的值(可以修改)。校验失败时抛出 ValueError,FastAPI 自动转为 422 响应。

跨字段校验

python
class DateRange(BaseModel):
    start: str
    end: str

    @validator("end")
    def end_must_after_start(cls, v, values):
        if "start" in values and v <= values["start"]:
            raise ValueError("结束时间必须晚于开始时间")
        return v

values 参数包含已经校验过的其他字段。注意:字段校验顺序按定义顺序执行,跨字段校验时要确保依赖的字段已经处理。

根校验器(校验整个模型)

python
from pydantic import root_validator

class Config(BaseModel):
    enabled: bool
    timeout: int = 30

    @root_validator
    def check_timeout_if_enabled(cls, values):
        if values.get("enabled") and values.get("timeout", 0) <= 0:
            raise ValueError("启用时 timeout 必须大于 0")
        return values

三、嵌套模型高级用法

列表嵌套

python
class Tag(BaseModel):
    name: str
    color: str = "#000000"

class ArticleCreate(BaseModel):
    title: str
    tags: list[Tag] = []

请求体:

json
{
  "title": "入门",
  "tags": [
    {"name": "FastAPI", "color": "#1890ff"},
    {"name": "Python"}
  ]
}

可选嵌套模型

python
from typing import Optional

class ArticleCreate(BaseModel):
    title: str
    author: Optional[Author] = None

author 可以传完整对象、传 null,或不传(默认 None)。

四、Config 配置

python
class ArticleCreate(BaseModel):
    title: str
    content: str

    class Config:
        min_anystr_length = 1    # 所有字符串字段默认最小长度 1
        anystr_strip_whitespace = True   # 自动去除字符串首尾空格
        validate_assignment = True        # 赋值时也触发校验

五、常见错误

校验器忘记返回值

python
@validator("hostname")
def check_hostname(cls, v):
    if not v:
        raise ValueError("不能为空")
    # 错误:没有 return,字段值变成 None

# 正确
@validator("hostname")
def check_hostname(cls, v):
    if not v:
        raise ValueError("不能为空")
    return v

用可变对象做默认值

python
# 错误:所有实例共享同一个列表
class Item(BaseModel):
    tags: list[str] = []

# 正确:用 default_factory
class Item(BaseModel):
    tags: list[str] = Field(default_factory=list)

校验器顺序依赖问题

python
class Model(BaseModel):
    b: int
    a: int

    @validator("a")
    def check_a(cls, v, values):
        # 错误:a 在 b 前面定义,校验 a 时 b 还没处理
        if v < values["b"]:   # KeyError
            ...

确保校验器依赖的字段在它之前定义,或者使用 @root_validator