如何基于Pydantic嵌套鉴别器优雅应用子列表约束?
基于Pydantic嵌套鉴别联合类型的子列表元素数量约束方案
问题背景
需要在Pydantic的嵌套鉴别联合类型列表中,对不同类型的元素分别应用数量约束:要求Cat类型元素至少1个(MinLen(1)),Dog类型元素最多2个(MaxLen(2)),且希望无需编写自定义验证器实现。
原模型代码如下:
from typing import Literal, Union from typing_extensions import Annotated from pydantic import BaseModel, Field, ValidationError class BlackCat(BaseModel): pet_type: Literal['cat'] color: Literal['black'] black_name: str class WhiteCat(BaseModel): pet_type: Literal['cat'] color: Literal['white'] white_name: str Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')] class Dog(BaseModel): pet_type: Literal['dog'] name: str Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')] class PetsModel(BaseModel): pets: list[Pet] # list of pet
已知可以对整个列表直接加全局约束,但无法区分类型:
from annotated_types import MaxLen, MinLen class PetsModel(BaseModel): pets: Annotated[list[Pet], MinLen(1), MaxLen(5)] # 全局约束,无法区分Cat/Dog
优雅解决方案(无需自定义验证器)
可以利用Pydantic的**计算字段(Computed Fields)**结合Annotated约束实现,通过计算字段分别统计Cat和Dog的数量,再对这些统计字段应用对应的约束:
from typing import Literal, Union, List from typing_extensions import Annotated from pydantic import BaseModel, Field, ValidationError, computed_field from annotated_types import MaxLen, MinLen class BlackCat(BaseModel): pet_type: Literal['cat'] color: Literal['black'] black_name: str class WhiteCat(BaseModel): pet_type: Literal['cat'] color: Literal['white'] white_name: str Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')] class Dog(BaseModel): pet_type: Literal['dog'] name: str Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')] class PetsModel(BaseModel): pets: list[Pet] @computed_field(return_type=Annotated[List[Cat], MinLen(1)]) def cats(self) -> List[Cat]: return [pet for pet in self.pets if isinstance(pet, (BlackCat, WhiteCat))] @computed_field(return_type=Annotated[List[Dog], MaxLen(2)]) def dogs(self) -> List[Dog]: return [pet for pet in self.pets if isinstance(pet, Dog)]
验证示例
- 符合约束的输入:
valid_data = { "pets": [ {"pet_type": "cat", "color": "black", "black_name": "black_cat_name"}, {"pet_type": "dog", "name": "dog_name"}, ] } model = PetsModel.model_validate(valid_data) print(model.cats) # 输出: [BlackCat(pet_type='cat', color='black', black_name='black_cat_name')] print(model.dogs) # 输出: [Dog(pet_type='dog', name='dog_name')]
- 违反Cat数量约束(无Cat):
invalid_data_no_cat = { "pets": [ {"pet_type": "dog", "name": "dog_name"}, ] } try: PetsModel.model_validate(invalid_data_no_cat) except ValidationError as e: print(e) # 提示 cats 字段长度至少为1
- 违反Dog数量约束(超过2只Dog):
invalid_data_too_many_dogs = { "pets": [ {"pet_type": "cat", "color": "black", "black_name": "black_cat_name"}, {"pet_type": "dog", "name": "dog1"}, {"pet_type": "dog", "name": "dog2"}, {"pet_type": "dog", "name": "dog3"}, ] } try: PetsModel.model_validate(invalid_data_too_many_dogs) except ValidationError as e: print(e) # 提示 dogs 字段长度最大为2
方案说明
- 利用
@computed_field自动统计对应类型的元素列表,无需手动编写验证逻辑 - 对计算字段的返回类型用
Annotated添加MinLen/MaxLen约束,Pydantic会自动验证统计结果 - 计算字段默认不会出现在模型的序列化输出中,若需调整可通过
serialization_alias参数配置
内容的提问来源于stack exchange,提问作者MOHAMMED BILAL ANSARI
相关产品推荐
相关产品推荐

