拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FastAPI 响应模型实战指南:返回类型注解、response_model 过滤与数据编码控制

FastAPI 响应模型实战指南返回类型注解、response_model 过滤与数据编码控制【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi本文基于 FastAPI 官方教程中的「Response Model响应模型」章节编写系统讲解如何用函数返回类型注解声明 API 响应结构以及response_model系列参数如何完成响应数据的校验、序列化、字段过滤与默认值裁剪。读完后你将能够安全地在响应中剔除敏感字段如明文密码、在编辑器/mypy 类型检查与 FastAPI 数据过滤之间兼得两者、精确控制 JSON 响应中保留哪些字段。文中所有代码均可在仓库的docs_src/response_model/目录下找到对应可运行示例。用返回类型注解声明响应结构FastAPI 通过路径操作函数的返回类型注解return type annotation来确定响应的类型。你可以像在函数参数中输入数据那样使用类型注解Pydantic 模型、列表、字典、以及整数、布尔值等标量值都可以。示例 1POST 返回单个ItemGET 返回list[Item]对应 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 会基于这个返回类型完成四件事校验要返回的数据。如果数据无效例如你漏掉了一个必填字段说明你的应用代码有 bug——它没有返回应该返回的内容。因此 FastAPI 会抛出一个服务器错误500而不是返回错误的数据。这样你和你的客户端都能确信拿到的是预期格式的正确数据。在 OpenAPI 的路径操作中添加响应的 JSON Schema。这个 Schema 会被自动文档使用也会被自动生成客户端代码的工具使用。使用 Pydantic序列化转成 JSON返回的数据。Pydantic 底层用Rust编写因此速度快得多。但最重要的一点是把输出数据限定并过滤为返回类型中定义的内容。这对安全性尤为关键详见下文「添加输出模型」一节。从源码结构看序列化过滤发生在 fastapi/routing.py 的内部响应序列化函数中响应字段最终通过 Pydantic 的serialize_json输出include、exclude、by_alias、exclude_unset、exclude_defaults、exclude_none等参数直接透传给 Pydantic参见 fastapi/routing.py#L495-L518。response_model参数有些场景下你想或必须返回的数据并不完全等于你声明的类型。例如你可能想返回一个字典或一个数据库对象但希望将其声明为 Pydantic 模型。这样该 Pydantic 模型就能替你承担所有关于该对象比如一个字典或数据库对象的文档、校验等工作。如果你加一个返回类型注解那么编辑器和工具正确地会报错说你的函数返回了一个类型比如Dict与你声明的比如一个 Pydantic 模型不一致。这种情况下你可以使用路径操作装饰器参数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等的一个参数而不是你的路径操作函数的参数——就像所有的请求参数和 Body 一样。response_model接受与声明 Pydantic 模型字段时相同的类型例如一个 Pydantic 模型也可以是一个 Pydantic 模型的list如list[Item]。FastAPI 会拿这个response_model去文档化、校验数据并且按照类型声明转换并过滤输出数据。提示如果你在编辑器里开着严格的类型检查mypy 等可以把函数返回类型声明为Any。这样你告诉编辑器你有意返回某个东西。而 FastAPI 依然会通过response_model完成对数据的文档化、校验、过滤等工作。response_model的优先级如果同时声明了返回类型和response_modelresponse_model具有优先级FastAPI 会使用它。这样你就可以给函数加上正确的类型注解供编辑器和 mypy 使用即使你返回的实际类型与响应模型不同。而 FastAPI 则依据response_model来校验和文档化数据。你还可以用response_modelNone来阻止为这个路径操作构建响应模型。如果你要为一些不是有效 Pydantic 字段的东西添加类型注解可能需要这么做——下文「无效返回类型注解」一节会给出例子。返回同样的输入数据下面声明一个UserIn模型其中包含明文密码对应 tutorial002_py310.pyfrom fastapi import FastAPI from pydantic import BaseModel, EmailStr app FastAPI() class UserIn(BaseModel): username: str password: str email: EmailStr full_name: str | None None # Dont do this in production! app.post(/user/) async def create_user(user: UserIn) - UserIn: return user注意要使用EmailStr需要先安装email-validator包。添加到你的项目中$ uv add email-validator或者$ uv add pydantic[email]我们用这个模型同时声明输入和输出数据async def create_user(user: UserIn) - UserIn: return user那么每当浏览器用一个带密码的UserIn创建用户时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尽管我们的路径操作函数返回的仍然是输入里那个包含密码的userreturn user……但因为我们声明了response_model为不含密码的UserOut模型app.post(/user/, response_modelUserOut)FastAPI会确保借助 Pydantic过滤掉所有未在输出模型中声明的数据。密码永远不会出现在响应里。response_model还是返回类型因为这两个模型不同如果把函数返回类型声明为UserOut编辑器会抱怨你返回了一个无效类型UserIn与UserOut是不同的类。所以在这个例子里我们必须使用response_model参数。……不过请继续往下读看另一种更优的解法。返回类型与数据过滤继续上面的例子。我们想给函数加上类型注解但实际上函数要返回包含更多数据的对象。我们希望 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的合法类型。FastAPI 的数据过滤而 FastAPI 会看到返回类型并确保返回的数据只包含该类型中声明的字段。FastAPI 内部做了多项 Pydantic 层面的处理以确保上面的类继承相似性规则不会被应用到返回数据的过滤上——否则你可能最终返回比预期更多的数据比如把password也带出去。这样就两全其美既有带工具支持的类型注解又有数据过滤。在文档中看到效果查看自动文档时可以看到输入模型和输出模型各自拥有独立的 JSON Schema而且两个模型都会被用于交互式 API 文档中其他返回类型注解有些情况下你要返回的东西不是有效的 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的子类类型注解是正确的。注解一个 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.pyfrom fastapi import FastAPI, Response from fastapi.responses import RedirectResponse app FastAPI() app.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的联合二选一。禁用响应模型接着上面的例子你可能不想要FastAPI 默认做的数据校验、文档化、过滤等但你仍然想给函数返回类型加注解以获得编辑器和类型检查器如 mypy的支持。这时可以设置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 应用的运行。响应模型的编码参数你的响应模型可能有默认值例如对应 tutorial004_py310.pyclass Item(BaseModel): name: str description: str | None None price: float tax: float 10.5 tags: list[str] []description: str | None NonePython 3.10 写法等价于Union[str, None]的默认值是Nonetax: float 10.5的默认值是10.5tags: list[str] []的默认值是空列表[]。但你可能希望当这些字段实际没有被赋值时把它们从响应中排除掉。比如当你在 NoSQL 数据库里有大量带可选属性的模型你不想发送一份塞满默认值的冗长 JSON 响应。使用response_model_exclude_unset参数可以设置路径操作装饰器参数response_model_exclude_unsetTrueitems { 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_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语义一致。从源码看这三个参数最终都透传给 Pydantic 的序列化调用在 fastapi/routing.py#L512-L514 中exclude_unset、exclude_defaults、exclude_none作为关键字参数传入serialize_json。并且路由层默认值均为Falsefastapi/routing.py#L384 有response_model_exclude_unset: bool False。带有默认值字段被显式赋值的数据但如果你的数据中为带默认值的字段赋了值比如 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等等。response_model_include与response_model_exclude你还可以在路径操作装饰器中使用response_model_include和response_model_exclude参数。它们接受一个字符串集合set ofstr这些字符串是属性名include表示只包含这些属性排除其他exclude表示排除这些属性只留其他。当你只有一个 Pydantic 模型、只想从输出中剔除几个字段时可以作为快捷方式使用对应 tutorial005_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]提示官方仍推荐使用上文的方案——即定义多个类继承 不同模型而不是这几个参数。原因是即使你用了response_model_include或response_model_exclude排除部分属性你的应用及其文档生成的 OpenAPI JSON 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]小结在路径操作装饰器中使用response_model参数来定义响应模型尤其用于确保私有数据被过滤掉例如密码绝不会泄漏到响应中当返回类型注解与response_model同时存在时response_model优先response_modelNone可完全禁用响应模型方便为任意返回类型如Response与非 Pydantic 对象的 Union保留类型注解用基类继承BaseUser/UserIn可以兼得编辑器的类型检查支持与 FastAPI 的数据过滤使用response_model_exclude_unset只返回显式设置过的值避免响应里塞满默认值并辅以response_model_include/response_model_exclude接受set、list或tuple做字段级快捷裁剪所有示例代码见 docs_src/response_model/ 目录核心过滤/序列化逻辑见 fastapi/routing.py。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门