尧图精选

FastAPI 响应模型实战指南:用返回类型注解与 response_model 控制 API 输出、验证与文档

🕒 发布时间:2026/9/8 23:36:01 📁 来源:尧图网络
FastAPI 响应模型实战指南用返回类型注解与 response_model 控制 API 输出、验证与文档【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi在 FastAPI 中API 的“出口”和“入口”一样需要精心控制既要保证返回的数据结构符合预期、不泄露敏感字段又要让自动文档和客户端代码生成工具拿到准确的 JSON Schema。本篇指南围绕官方文档 response-model.md 展开系统讲解如何通过返回类型注解和response_model参数完成响应数据的验证、过滤与序列化并结合 FastAPI 源码 揭示其底层实现机制。读完后你将能够用类型注解让 FastAPI 自动验证并过滤响应数据、用继承模型兼顾静态类型检查与字段裁剪、用response_model_exclude_unset等参数精确控制输出字段。声明返回类型FastAPI 用它做四件事你可以通过在路径函数上注解返回类型来声明响应的数据类型。用法与在函数参数中为输入数据使用类型注解完全相同可以使用 Pydantic 模型、列表、字典、标量值整数、布尔值等。以下示例源码见 tutorial001_01_py310.pyfrom fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Item(BaseModel): name: str description: str | None None price: float tax: float | None None tags: list[str] [] app.post(/items/) async def create_item(item: Item) - Item: return item app.get(/items/) async def read_items() - list[Item]: return [ Item(namePortal Gun, price42.0), Item(namePlumbus, price32.0), ]FastAPI 会利用这个返回类型完成以下工作验证返回的数据。如果数据无效例如缺少某个字段说明你的应用代码有缺陷——它没有返回它应该返回的东西。此时 FastAPI 会向客户端返回一个服务器错误而不是把错误数据发出去。这样你和你的客户端都可以确定收到的是预期的数据与预期的结构。在该路径的 OpenAPI 中为响应添加JSON Schema。该 Schema 会被自动文档Swagger UI使用。也会被客户端代码自动生成工具使用。使用Pydantic把返回数据序列化为 JSON。Pydantic 核心用Rust编写序列化会快得多。但最关键的是它会限制并过滤输出数据只保留返回类型中声明的内容。这对安全尤为重要见下文。源码视角返回注解如何变成响应字段从源码结构看这一机制在 routing.py 的get_route()中实现当开发者没有显式传response_model时参数值是DefaultPlaceholder占位符FastAPI 会读取端点的返回注解——if isinstance(response_model, DefaultPlaceholder): return_annotation get_typed_return_annotation(endpoint) if lenient_issubclass(return_annotation, Response): response_model None else: # ... response_model return_annotation即返回注解自动成为 response_model但如果返回注解是Response或其子类则自动把response_model置为None后文“直接返回 Response”一节正是依赖这段逻辑。确定后FastAPI 用该类型创建一个modeserialization的序列化字段route.response_field请求处理时再由serialize_response()调用它详见后文“底层实现”一节。参数response_model当你返回的数据与声明类型不一致时存在一类常见场景你需要返回的数据与声明的类型并不完全一致。例如你可能想返回一个字典或数据库对象却希望将其声明为 Pydantic 模型。这样 Pydantic 模型就可以替你完成文档、验证等全部工作。如果你同时加上返回类型注解编辑器和工具会正确地报错函数实际返回的类型比如dict与声明的类型比如 Pydantic 模型不同。这种情况下可以使用路径装饰器的response_model参数来代替返回类型注解。response_model可以用于任何路径装饰器app.get()app.post()app.put()app.delete()等等。示例源码见 tutorial001_py310.pyfrom typing import Any from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Item(BaseModel): name: str description: str | None None price: float tax: float | None None tags: list[str] [] app.post(/items/, response_modelItem) async def create_item(item: Item) - Any: return item app.get(/items/, response_modellist[Item]) async def read_items() - Any: return [ {name: Portal Gun, price: 42.0}, {name: Plumbus, price: 32.0}, ]注意response_model是装饰器方法get、post等的参数而不是你路径函数的参数。这一点与所有请求参数和请求体参数不同。response_model接收的类型与你在 Pydantic 模型中声明一个字段时写的类型相同所以它既可以是 Pydantic 模型也可以是例如 Pydantic 模型的list如List[Item]。FastAPI 会用这个response_model完成数据文档、验证等全部工作并按它声明的类型转换和过滤输出数据比如上例中直接返回裸字典最终输出仍会被Item约束。技巧如果你在编辑器或 mypy 中启用了严格的类型检查可以把函数的返回类型声明为Any。这样你是在告诉编辑器“我有意返回任意东西”但 FastAPI 依然会用response_model完成文档、验证、过滤等全部工作。response_model的优先级如果你同时声明了返回类型注解和response_modelFastAPI 使用的是response_model它拥有优先级。这样做的好处是即使你实际返回的类型与响应模型不同你仍然可以给函数加上正确的类型注解供编辑器和 mypy 等工具使用同时让 FastAPI 继续用response_model做数据验证、文档生成等。你也可以使用response_modelNone来禁用这个路径的响应模型生成——如果你需要为一些不是有效 Pydantic 字段的东西添加类型注解时就会用到这一点下文的“其他返回类型注解”一节有示例。返回与输入相同的数据一个典型的安全陷阱来看一个反面教材源码见 tutorial002_py310.py。这里声明了一个UserIn模型其中包含明文密码from fastapi import FastAPI from pydantic import BaseModel, EmailStr app FastAPI() class UserIn(BaseModel): username: str password: str email: EmailStr full_name: str | None None注意若要使用EmailStr需要安装email-validator依赖可用以下命令添加到项目中$ uv add email-validator或$ uv add pydantic[email]然后用这个模型同时声明输入和输出# 切勿在生产环境这样做 app.post(/user/) async def create_user(user: UserIn) - UserIn: return user危险切勿以明文存储用户密码更不要这样在响应中把它发出去——除非你完全了解所有风险并确切知道自己在做什么。此时每当浏览器创建一个带密码的用户API 就会在响应中把同样的密码原样返回。对创建者本人而言也许问题不大毕竟密码是他自己刚发的但如果把同一个模型用在其他路径上就可能把用户密码发给所有客户端。增加一个输出模型把密码挡在门外正确做法是创建一个含明文密码的输入模型和一个不含密码的输出模型源码见 tutorial003_py310.pyfrom typing import Any from fastapi import FastAPI from pydantic import BaseModel, EmailStr app FastAPI() 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(/user/, response_modelUserOut) async def create_user(user: UserIn) - Any: return user在这里即使路径函数返回的是包含密码的同一个输入user对象return user……因为我们把response_model声明为不含密码的UserOut模型app.post(/user/, response_modelUserOut)……FastAPI 会自动过滤掉所有未在输出模型中声明的数据借助 Pydantic。为什么这里必须用response_model而不是返回类型由于两个模型是不同的类如果给函数返回类型注解UserOut编辑器和工具会抱怨我们返回了无效类型——因为UserIn并不是UserOut的实例。所以在这个例子里只能声明response_model。……不过请继续往下看有一个两全其美的替代方案。返回类型注解与数据过滤用继承兼顾两者延续上一个例子。我们想用类型注解标注函数同时希望函数能返回包含更多数据的东西例如内部的UserIn。我们希望 FastAPI 继续用响应模型过滤数据即使函数返回了更多数据最终响应也只包含响应模型中声明的字段。上一节中由于类不同我们被迫使用response_model参数代价是失去了编辑器与工具对返回类型的检查支持。但在这类场景里我们通常只是想过滤/删掉一部分数据。这时可以用类和继承同时获得类型注解的工具支持与 FastAPI 的数据过滤源码见 tutorial003_01_py310.pyfrom fastapi import FastAPI from pydantic import BaseModel, EmailStr app FastAPI() class BaseUser(BaseModel): username: str email: EmailStr full_name: str | None None class UserIn(BaseUser): password: str app.post(/user/) async def create_user(user: UserIn) - BaseUser: return user这样既获得编辑器、mypy 等工具的支持因为代码在类型上是完全正确的又获得 FastAPI 的数据过滤。这是两者兼得的经典模式。类型注解与工具视角先看编辑器、mypy 等工具如何理解这段代码BaseUser定义基础字段UserIn继承BaseUser并新增password字段因此包含两个模型的全部字段。我们把函数返回类型注解为BaseUser但实际返回的是UserIn实例。编辑器、mypy 等工具不会报错就类型系统而言UserIn是BaseUser的子类即当期望值是BaseUser类型时返回UserIn是合法的。FastAPI 视角的数据过滤对 FastAPI 来说它会看到返回类型并保证返回的内容只包含该类型中声明的字段。FastAPI 内部做了额外处理借助 Pydantic 的序列化模式确保类的继承关系不会反过来用于响应数据的过滤——否则你可能返回比预期多得多的数据。于是你就获得了最佳组合工具友好的类型注解 自动数据过滤。这一行为的针对性测试可以在 test_response_model_sub_types.py 中找到字段过滤逻辑则由 test_response_model_data_filter.py 覆盖。在自动文档中查看效果在自动生成的文档中你可以看到输入模型和输出模型各有自己的 JSON Schema且两者都会用于交互式 API 文档其他返回类型注解非 Pydantic 类型的处理你可能需要返回一些不是有效 Pydantic 字段的东西并仍然在函数里注解它以获取编辑器和 mypy 等工具的支持。以下是几种情况直接返回 Response最常见的情况是直接返回一个 Response 对象高级教程中有专门讲解源码见 tutorial003_02_py310.pyfrom fastapi import FastAPI, Response from fastapi.responses import JSONResponse, RedirectResponse app FastAPI() app.get(/portal) async def get_portal(teleport: bool False) - Response: if teleport: return RedirectResponse(urlhttps://www.youtube.com/watch?vdQw4w9WgXcQ) return JSONResponse(content{message: Heres your interdimensional portal.})这个简单情形由 FastAPI 自动处理因为返回类型注解是Response类或其子类。同时工具也满意因为RedirectResponse和JSONResponse都是Response的子类类型注解是正确的。这里的“自动处理”正是上一节源码中lenient_issubclass(return_annotation, Response)分支的作用response_model被自动置为NoneFastAPI 不会尝试为Response创建响应模型。注解为 Response 的子类也可以在注解里直接使用Response的某个子类源码见 tutorial003_03_py310.pyfrom fastapi import FastAPI from fastapi.responses import RedirectResponse app FastAPI() app.get(/teleport) async def get_teleport() - RedirectResponse: return RedirectResponse(urlhttps://www.youtube.com/watch?vdQw4w9WgXcQ)同样能正常工作因为RedirectResponse是Response的子类FastAPI 自动处理这种简单情形。无效的返回类型注解但如果你返回的是某个任意的、不是有效 Pydantic 类型的对象例如数据库对象并在函数里这样注解FastAPI 会尝试从该注解创建 Pydantic 响应模型然后失败。同理如果你返回的是多个类型之间的一个并集union即“其中任意一种类型”而其中一或多个不是有效 Pydantic 类型也会失败例如下面这个例子会 报错源码见 tutorial003_04_py310.pyapp.get(/portal) async def get_portal(teleport: bool False) - Response | dict: if teleport: return RedirectResponse(urlhttps://www.youtube.com/watch?vdQw4w9WgXcQ) return {message: Heres your interdimensional portal.}失败的原因是该类型注解既不是 Pydantic 类型也不是单纯的Response类或其子类而是一个Response与dict之间的并集二者之一。相关失败行为可在 test_response_model_invalid.py 中查到。禁用响应模型response_modelNone回到上面的例子你可能不想要 FastAPI 默认的验证、文档、过滤等行为但仍然希望保留函数的返回类型注解以继续获得编辑器和类型检查工具的支持。这时可以把response_modelNone设为显式参数来禁用响应模型生成源码见 tutorial003_05_py310.pyfrom fastapi import FastAPI, Response from fastapi.responses import RedirectResponse app FastAPI() app.get(/portal, response_modelNone) async def get_portal(teleport: bool False) - Response | dict: if teleport: return RedirectResponse(urlhttps://www.youtube.com/watch?vdQw4w9WgXcQ) return {message: Heres your interdimensional portal.}这样 FastAPI 会忽略响应模型的生成你可以拥有任意需要的返回类型注解而不会影响 FastAPI 应用的行为。细节提示response_modelNone必须是显式传入的None。在 applications.py 的装饰器签名中未传参时该参数是DefaultPlaceholder占位符表示“回退到返回注解”只有显式传None才会真正禁用响应模型。响应模型的序列化参数精细控制输出字段你的响应模型字段可以有默认值例如源码见 tutorial004_py310.pyclass Item(BaseModel): name: str description: str | None None price: float tax: float 10.5 tags: list[str] [] items { 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: []}, }description: str | None None的默认值是None。tax: float 10.5的默认值是10.5。tags: list[str] []的默认值是空列表[]。但你可能希望在数据中这些字段未被实际设置时把它们从结果中省略。例如当你在 NoSQL 数据库中有大量可选属性的模型但不想发送一个塞满默认值的超长 JSON 响应时。使用参数response_model_exclude_unset可以把路径装饰器的参数设为response_model_exclude_unsetTrueapp.get(/items/{item_id}, response_modelItem, response_model_exclude_unsetTrue) async def read_item(item_id: str): return items[item_id]这样这些默认值就不会出现在响应中只有实际被设置的值才会出现。因此对 ID 为foo的物品发起请求时响应不含默认值将是{ name: Foo, price: 50.2 }注意你还可以使用response_model_exclude_defaultsTrueresponse_model_exclude_noneTrue分别对应 Pydantic 序列化文档中exclude_defaults和exclude_none的语义。带有实际值的默认字段但如果数据中确实为那些有默认值的字段赋了值就像 ID 为bar的物品{ name: Bar, description: The bartenders, price: 62, tax: 20.2 }它们就会包含在响应中。与默认值相同的数据如果数据中的值与默认值相同就像 ID 为baz的物品{ name: Baz, description: None, price: 50.2, tax: 10.5, tags: [] }FastAPI准确说是 Pydantic足够聪明它能理解即使description、tax、tags与默认值相同它们也是被显式设置的而不是取自默认值因此仍然会包含在 JSON 响应里。技巧注意默认值可以是任意值不只是None。它可以是列表[]、浮点数10.5等。这正是exclude_unset排除“未显式设置”比exclude_defaults排除“等于默认值”更“智能”的原因。response_model_include与response_model_exclude还可以使用路径装饰器的response_model_include和response_model_exclude参数。它们接受一个str的set指明要包含的属性名省略其余或要排除的属性名包含其余app.get( /items/{item_id}/name, response_modelItem, response_model_include{name, description}, ) async def read_item_name(item_id: str): return items[item_id] app.get(/items/{item_id}/public, response_modelItem, response_model_exclude{tax}) async def read_item_public_data(item_id: str): return items[item_id]源码见 tutorial005_py310.py如果只有一个 Pydantic 模型又想从输出中删掉某些数据这是一个快速捷径。技巧不过仍然推荐优先使用前面“多模型 继承”的思路而不是这些参数。因为即使你用response_model_include或response_model_exclude省略了某些属性应用 OpenAPI以及文档中生成的 JSON Schema 依然是完整模型的 Schema。response_model_by_alias的行为类似。技巧{name, description}这种字面量语法会创建一个包含这两个值的set等价于set([name, description])。误用list代替set也没问题如果你忘记使用set改用了list或tupleFastAPI 依然会把它转换为set并正常工作源码见 tutorial006_py310.pyapp.get( /items/{item_id}/name, response_modelItem, response_model_include[name, description], ) async def read_item_name(item_id: str): return items[item_id] app.get(/items/{item_id}/public, response_modelItem, response_model_exclude[tax]) async def read_item_public_data(item_id: str): return items[item_id]底层实现FastAPI 如何在请求处理时应用这些规则上面的文档级行为最终落实在 fastapi/routing.py 的响应序列化函数serialize_response()L301–L341中。其核心逻辑async def serialize_response( *, field: ModelField | None None, response_content: Any, include: IncEx | None None, exclude: IncEx | None None, by_alias: bool True, exclude_unset: bool False, exclude_defaults: bool False, exclude_none: bool False, ... ) - Any: if field: # 1. 先验证返回值 value, errors field.validate(response_content, {}, loc(response,)) if errors: raise ResponseValidationError( errorserrors, bodyresponse_content, endpoint_ctxctx, ) # 2. 再按 include/exclude/by_alias/exclude_* 规则序列化 serializer field.serialize_json if dump_json else field.serialize return serializer( value, includeinclude, excludeexclude, by_aliasby_alias, exclude_unsetexclude_unset, exclude_defaultsexclude_defaults, exclude_noneexclude_none, ) else: # 没有响应模型时退化为通用编码 return jsonable_encoder(response_content)对照文档可以印证几条关键结论“验证 数据错误 → 500”字段验证失败时抛出ResponseValidationError即服务器错误而非 4xx这正是文档中“如果你自己的代码返回了错误数据FastAPI 会返回服务器错误而不是错误数据”的出处过滤规则原样透传response_model_include/exclude以include/exclude传入response_model_exclude_unset/exclude_defaults/exclude_none三个布尔开关分别对应exclude_unset/exclude_defaults/exclude_noneresponse_modelNone的退路没有响应字段时走jsonable_encoder()通用编码路径即不验证、不过滤、不生成响应 Schema继承模型不“泄漏”route.response_field在 get_route() 中以modeserialization创建Pydantic 序列化模式下只输出声明在注解类型上的字段父类多出来的子类字段如password会被剔除这解释了为什么- BaseUser注解下返回UserIn不会把密码带出去。include/exclude参数被传入list而非set时也能工作的容错行为以及默认值排除的语义分别由 test_response_model_include_exclude.py、test_response_model_default_factory.py 等测试文件守护返回注解与response_model的关系则由 test_response_model_as_return_annotation.py 覆盖。小结用路径装饰器的response_model参数定义响应模型——首要目的是保证敏感数据被过滤出去同时它驱动 OpenAPI JSON Schema 生成与自动文档。需要过滤部分字段时优先考虑BaseUser/UserIn式的继承模型 返回类型注解兼顾 mypy 类型检查与 FastAPI 过滤类型不一致时用response_model 返回类型Any。用response_model_exclude_unset只返回显式设置的值按需叠加response_model_exclude_defaults、response_model_exclude_none。直接返回Response或其子类可被 FastAPI 自动识别对非 Pydantic 并集类型用response_modelNone显式禁用响应模型保留类型注解的工具支持。response_model_include/exclude是单模型场景的快速捷径但记住 OpenAPI Schema 仍会是完整模型——长期方案仍然是拆分多个模型。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →