FastAPI 响应模型指南:从 JSON 数据定义到动态管理的实践

FastAPI 响应模型指南:从 JSON 数据定义到动态管理的实践

本篇文章详细介绍了如何在 FastAPI 中使用响应模型,包括在路径操作函数中声明 response_model、处理请求与响应数据不同时的场景,以及通过参数如 response_model_exclude_unset 来优化响应数据。文中还探讨了如何使用 response_model_includeresponse_model_exclude 参数动态控制响应字段的显示,帮助开发者轻松精简响应内容。通过丰富的代码示例和最佳实践建议,您将全面掌握如何定义和管理响应模型,从而确保接口数据的准确性和可维护性,为构建高质量 API 提供坚实基础。

在 API 开发中,响应的数据结构直接影响客户端对接口的理解和使用体验。FastAPI 提供了强大的 response_model 功能,通过 Pydantic 模型严格定义接口的响应数据格式,使开发者能够更高效地管理数据结构。以下示例中使用的 Python 版本为 Python 3.10.15,FastAPI 版本为 0.115.4

一 操作函数中声明响应模型

在 FastAPI 中,可以通过使用 response_model 参数来指定路径操作(如 getpost 等方法)的响应模型。

from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.post("/items01/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item


@app.get("/items02/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]

不同于 路径操作函数 的其他参数和请求体,response_model 直接作为装饰器方法的参数。此参数接受一个 Pydantic 模型或一个 Pydantic 模型的列表,例如 List[Item] 。这样设置后,响应数据将被限制在指定模型的结构内。运行代码文件 chapter15.py 来启动应用:

$ uvicorn chapter15:app --reload

SwaggerUI 中可以查看在线文档:http://127.0.0.1:8000/docs

二 请求与响应数据相同

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None
    
@app.post("/user01/")
async def create_user(user: UserIn) -> UserIn:
    return user

定义了一个包含明文密码的 UserIn 模型,用于处理输入和输出数据。这导致在通过浏览器提交密码创建用户时,API 也会在响应中包含密码。虽然 Pydantic 模型推荐复用,但在其他路径操作中使用相同模型可能会导致密码意外泄露给所有客户端。

划重点

永远不要存储或发送用户的明文密码。

三 请求和响应数据不同

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None

@app.post("/user02/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user

可以创建一个包含明文密码的输入模型 UserIn 和一个不包含密码的输出模型 UserOut。这样,即使路径操作函数返回了包含密码的输入模型,通过将 response_model 设置为 UserOut,FastAPI 将利用 Pydantic 自动过滤掉输出模型中未声明的所有数据。

四 响应数据模型设置默认值

响应模型的数据可以设置默认值。

class Item01(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []


items01 = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item01, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items01[item_id]

路径操作装饰器 中设置参数 response_model_exclude_unset=True推荐使用)以排除未显式设置的字段。这有助于确保响应 JSON 中只包含实际设定的字段值。

1 排除默认数据

原始响应 JSON :

{
  "name": "Foo",
  "description": null,
  "price": 50.2,
  "tax": 10.5,
  "tags": []
}

未显式设置的字段在响应 JSON 中不包含:

{
  "name": "Foo",
  "price": 50.2
}
2 默认值字段有实际数据

看 bar 数据:

{
  "name": "Bar",
  "description": "The bartenders",
  "price": 62,
  "tax": 20.2
}
3 默认值与实际数据相同

看 baz 数据:

{
  "name": "Baz",
  "description": null,
  "price": 50.2,
  "tax": 10.5,
  "tags": []
}

在 FastAPI 中,使用 response_model_exclude_unset=True 可有效控制哪些字段包含在响应中,帮助精简响应并避免发送无用或默认数据。此外,还有其他相关参数可以用于细化控制响应数据:

参数描述使用场景
response_model_exclude_unset排除在输出模型中未显式设置的字段。用于防止未显式设置的字段被包含在响应中。
response_model_exclude_defaults排除等于模型中默认值的字段。用于避免包含未明确更改的默认值字段。
response_model_exclude_none排除在输出模型中设为 None 的字段。用于从最终响应中省略明确设置为 None 的字段。

推荐使用response_model_exclude_unset=True,以确保响应中仅包含有意义的数据。

五 include/exclude 管理响应模型

FastAPI允许通过 response_model_includeresponse_model_exclude 参数来管理响应数据中包含或排除的字段。这些参数接收一个属性名称 str 组成的 set ,分别用于指定响应中 应包含的字段(排除其他字段)或 应排除的字段(包含其他字段)。

class Item02(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5


items02 = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item02,
    response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
    return items02[item_id]


@app.get("/items/{item_id}/public", response_model=Item02, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
    return items02[item_id]


@app.get(
    "/items/{item_id}/name01",
    response_model=Item02,
    response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
    return items02[item_id]


@app.get("/items/{item_id}/public01", response_model=Item02, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
    return items02[item_id]

注:使用 response_model_include response_model_exclude 参数来控制响应字段,生成的OpenAPI 定义和文档中的 JSON Schema 仍将显示完整的数据模型。

推荐使用不同的类来定义不同的响应模型,而不是依赖这些参数,这样做不仅可以清晰地管理数据结构,还可以确保 API 文档的 准确性 和 易用性。

使用列表或元组代替集合
@app.get(
    "/items/{item_id}/name",
    response_model=Item02,
    response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
    return items02[item_id]
  
  
@app.get(
    "/items/{item_id}/name01",
    response_model=Item02,
    response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
    return items02[item_id]

使用 {"name", "description"} 语法可以快速创建一个包含这两个元素的集合(set),这与使用 set(["name", "description"]) 效果相同。如果误用 ["name", "description"] 列表(list)或元组(tuple)代替集合(set),FastAPI 会自动将其转换为集合,并且功能仍然正常运行。

六 完整代码示例

from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.post("/items01/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item


@app.get("/items02/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user01/")
async def create_user(user: UserIn) -> UserIn:
    return user


@app.post("/user02/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user


class Item01(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []


items01 = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item01, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items01[item_id]


class Item02(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5


items02 = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item02,
    response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
    return items02[item_id]


@app.get("/items/{item_id}/public", response_model=Item02, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
    return items02[item_id]


@app.get(
    "/items/{item_id}/name01",
    response_model=Item02,
    response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
    return items02[item_id]


@app.get("/items/{item_id}/public01", response_model=Item02, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
    return items02[item_id]
  

七 源码地址

详情见:GitHub FastApiProj

八 参考

[1] FastAPI 文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码不忘补水

感谢有你,让我的创作更有价值!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值