使用ModelSerializer时如何保证API规格正确?
问题:用model_serializer序列化列表为字典后API规格不正确
我用Pydantic的model_serializer把列表序列化为字典(感谢Plagon),但生成的API规格不正确。以下是实现代码及当前错误的API spec,请问如何确保API规格正确?
实现代码
from uuid import UUID, uuid4 from fastapi import FastAPI from pydantic import BaseModel, Field, model_serializer class Item(BaseModel): uid: UUID = Field(default_factory=uuid4) number: int = Field(default_factory=lambda: 4) # https://xkcd.com/221/ class ResponseModel(BaseModel): items: list[Item] @model_serializer def serialize_model(self): return {str(item.uid): item.model_dump() for item in self.items} app = FastAPI() @app.get("/items/", response_model=ResponseModel) def read_item(): return ResponseModel(items=[Item() for _ in range(2)])
错误的API spec
{ "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" }, "paths": { "/items/": { "get": { "summary": "Read Item", "operationId": "read_item_items__get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResponseModel" } } } } } } } }, "components": { "schemas": { "Item": { "properties": { "uid": { "type": "string", "format": "uuid", "title": "Uid" }, "number": { "type": "integer", "title": "Number" } }, "type": "object", "title": "Item" }, "ResponseModel": { "properties": { "items": { // <- 此处错误,因为它不知道items现在是一个dict了。 "items": { "$ref": "#/components/schemas/Item" }, "type": "array", "title": "Items" } }, "type": "object", "required": [ "items" ], "title": "ResponseModel" } } } }
解决方案
问题原因
FastAPI的API规格基于Pydantic模型的字段类型生成,你的ResponseModel中items字段定义为list[Item],即便通过model_serializer将其序列化为字典,生成的schema仍会保留数组类型,导致与实际输出不匹配。
可行方案
方案1:修改模型字段类型,添加列表转字典的构造方法
直接将ResponseModel的items字段定义为dict[str, Item],并添加类方法适配列表输入,这样生成的schema会正确反映输出结构:
from uuid import UUID, uuid4 from fastapi import FastAPI from pydantic import BaseModel, Field class Item(BaseModel): uid: UUID = Field(default_factory=uuid4) number: int = Field(default_factory=lambda: 4) # https://xkcd.com/221/ class ResponseModel(BaseModel): items: dict[str, Item] @classmethod def from_item_list(cls, items: list[Item]): return cls(items={str(item.uid): item for item in items}) app = FastAPI() @app.get("/items/", response_model=ResponseModel) def read_item(): return ResponseModel.from_item_list([Item() for _ in range(2)])
方案2:直接返回字典,指定响应模型类型
如果不需要保留ResponseModel,可直接在路由中构造字典,并通过response_model参数指定输出类型:
from uuid import UUID, uuid4 from fastapi import FastAPI from pydantic import BaseModel, Field class Item(BaseModel): uid: UUID = Field(default_factory=uuid4) number: int = Field(default_factory=lambda: 4) # https://xkcd.com/221/ app = FastAPI() @app.get("/items/", response_model=dict[str, Item]) def read_item(): items = [Item() for _ in range(2)] return {str(item.uid): item for item in items}
验证效果
修改后生成的API spec中,items字段会被正确定义为字典类型:
"items": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/Item" }, "title": "Items" }
内容的提问来源于stack exchange,提问作者tback
相关产品推荐
相关产品推荐

