You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Pydantic中分离Schema验证与逻辑/功能验证?

问题描述

我定义了如下User模型,其中包含Schema验证(如字段类型、EmailStr)及针对company字段的逻辑验证:

class User(BaseModel):
    name: str
    email: EmailStr
    phone_number: str
    job_title: str
    company: str
    salary: int
    
    @field_validator('company')
    def valid_company(cls, company: str) -> str:
        # validate that the company exists...
        return company

我希望让开发者能够分别进行Schema验证与逻辑验证,因此想到通过继承拆分模型:

class User(BaseModel):
    name: str
    email: EmailStr
    phone_number: str
    job_title: str
    company: str
    salary: int

class UserValidator(User):
    @field_validator('company')
    def valid_company(cls, company: str) -> str:
        # validate that the company exists...
        return company

但User模型被其他模型(如Account)引用时,需要重写字段来替换为验证版模型:

class Account(BaseModel):
    user: User
    ...


class AccountValidator(Account):
    user: UserValidator

    custom_validators...

请问是否有更优的实现方式?或可借助其他库来实现?

优化实现方案

方法1:使用Pydantic泛型模型

通过泛型让父模型动态引用不同的子模型,避免重复定义AccountValidator这类衍生模型,实现复用性:

from typing import Generic, TypeVar
from pydantic import BaseModel, EmailStr, field_validator

T = TypeVar('T')

class User(BaseModel):
    name: str
    email: EmailStr
    phone_number: str
    job_title: str
    company: str
    salary: int

class UserValidator(User):
    @field_validator('company')
    def valid_company(cls, company: str) -> str:
        # 模拟公司存在验证逻辑
        if company not in ["Google", "Microsoft", "Apple"]:
            raise ValueError(f"Company {company} does not exist")
        return company

class Account(BaseModel, Generic[T]):
    user: T
    account_id: str

# 仅做Schema验证的Account类型
AccountSchema = Account[User]
# 包含逻辑验证的Account类型
AccountWithValidation = Account[UserValidator]

开发者可根据需求直接使用AccountSchema或AccountWithValidation,无需重复编写模型结构。

方法2:通过验证器开关控制

在基础模型中添加可选的逻辑验证开关,通过model_config控制验证是否启用,无需拆分模型:

from pydantic import BaseModel, EmailStr, field_validator, ValidationInfo

class User(BaseModel):
    name: str
    email: EmailStr
    phone_number: str
    job_title: str
    company: str
    salary: int

    model_config = {"validate_company": False}

    @field_validator('company')
    def valid_company(cls, company: str, info: ValidationInfo) -> str:
        if info.model_config.get("validate_company"):
            # 执行逻辑验证
            if company not in ["Google", "Microsoft", "Apple"]:
                raise ValueError(f"Company {company} does not exist")
        return company

# 仅执行Schema验证
user_schema = User(
    name="John", 
    email="john@example.com", 
    phone_number="123456", 
    job_title="Engineer", 
    company="Test", 
    salary=100000
)
# 启用逻辑验证
user_with_validation = User(
    name="John", 
    email="john@example.com", 
    phone_number="123456", 
    job_title="Engineer", 
    company="Google", 
    salary=100000,
    model_config={"validate_company": True}
)

对于引用User的Account模型,同样可以通过model_config传递验证开关:

class Account(BaseModel):
    user: User
    account_id: str

# 启用Account中user的逻辑验证
account_with_validation = Account(
    user=User(
        name="John", 
        email="john@example.com", 
        phone_number="123456", 
        job_title="Engineer", 
        company="Google", 
        salary=100000
    ),
    account_id="acc_123",
    model_config={"validate_company": True}
)

方法3:分离逻辑验证到独立函数

把逻辑验证从模型中抽离为独立函数,开发者可在Schema验证完成后手动调用,实现完全的解耦:

from pydantic import BaseModel, EmailStr, ValidationError

class User(BaseModel):
    name: str
    email: EmailStr
    phone_number: str
    job_title: str
    company: str
    salary: int

def validate_user_company(user: User) -> User:
    # 逻辑验证逻辑
    if user.company not in ["Google", "Microsoft", "Apple"]:
        raise ValueError(f"Company {user.company} does not exist")
    return user

# 仅执行Schema验证
user_schema = User(
    name="John", 
    email="john@example.com", 
    phone_number="123456", 
    job_title="Engineer", 
    company="Test", 
    salary=100000
)
# 额外执行逻辑验证
try:
    user_validated = validate_user_company(user_schema)
except ValueError as e:
    print(e)

这种方式灵活性最高,适合在API请求处理等场景中分步执行验证流程。

内容的提问来源于stack exchange,提问作者Kevin Nammour

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:59:53