Python 数据契约层FastAPI+Pydantic(二)
Pydantic负责在数据进入和离开应用时执行验证和转换,FastAPI则把这个能力接入HTTP请求/响应生命周期。
Pydantic+FastAPI+typing范型,让model可以参数化
一 FastAPI + Pydantic + typing联用
python中的泛型通过TypeVar与Generic实现,Generic[T]让class变成泛型类,在实例化或子类化时指定具体类型,但在Pydantic V2起,直接继承BaseModel和Generic[T],即可定义泛型model
最常用的场景,响应包装器Response Model
from typing import Generic, TypeVar, Optional from pydantic import BaseModel T = TypeVar("T") class ApiResponse(BaseModel, Generic[T]): code: int = 200 message: str = "success" data: Optional[T] = None使用时,ApiResponse[UserResponse]就是一个具体的、可验证的模型
class UserResponse(BaseModel): id: int username: str resp = ApiResponse[UserResponse]( data={"id": 1, "username": "alice"} ) # data 会被验证并构造为 UserResponse 实例在FastAPI中直接使用泛型模型:
@app.get("/users/me", response_model=ApiResponse[UserResponse]) async def get_me(): return {"code": 200, "data": {"id": 1, "username": "alice"}}二 Pydantic + Union
三个独立的model共享同一的基类时,比如一个接口可能接收三个独立class model的参数,此时可以用Pydantic + Union实现
class Shape(BaseModel): kind: str class Circle(Shape): kind: Literal["circle"] radius: float class Square(Shape): kind: Literal["square"] side: float class Triangle(Shape): kind: Literal["triangle"] base: float height: float声明一个共享的基类,类型可能是Circle、Square、Triangle的其中一种model
ShapeUnion = Union[Circle, Square, Triangle]但直接用Union有坑:Pydantic v2 按顺序逐个尝试,Circle验证失败才试Square,错误信息分散、性能也差。更推荐可辨识联合(discriminated union),用kind字段做判别:
from typing import Annotated from pydantic import Field ShapeUnion = Annotated[ Union[Circle, Square, Triangle], Field(discriminator="kind"), ]避坑:这样 Pydantic 直接按kind的值路由到唯一模型,验证精确、报错清晰、还更快
@app.post("/shapes/") async def create_shape(shape: ShapeUnion): ...三 field_validator、model_validator
两者都可以配置model = before/after,field_validator只能验证单个字段,model_validator可以验证整个模型/输入数据
from pydantic import BaseModel, field_validator, model_validator, ValidationInfo class User(BaseModel): name: str age: int password: str confirm: str @field_validator("name", mode="before") @classmethod def strip_name(cls, v): # before:v 是原始输入,可能是 " alice " if isinstance(v, str): return v.strip() return v @field_validator("age") @classmethod def check_age(cls, v: int, info: ValidationInfo): # after(默认):v 已经是 int if v < 0: raise ValueError("age 不能为负") return v @model_validator(mode="after") def check_password(self): # self 是已构造好的实例,字段都已验证 if self.password != self.confirm: raise ValueError("两次密码不一致") return self与 Union / 判别联合结合:每个分支独立校验
from typing import Annotated, Literal, Union from pydantic import Field class Circle(BaseModel): kind: Literal["circle"] radius: float @field_validator("radius") @classmethod def radius_positive(cls, v: float): if v <= 0: raise ValueError("radius 必须为正") return v class Square(BaseModel): kind: Literal["square"] side: float @model_validator(mode="after") def check_side(self): if self.side <= 0: raise ValueError("side 必须为正") return self Shape = Annotated[Union[Circle, Square], Field(discriminator="kind")]注:代码是AI写的,理论是我自己整理的