ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FastAPI 高级指南:在 OpenAPI 中声明附加响应(Additional Responses)的完整实战解析

FastAPI 高级指南:在 OpenAPI 中声明附加响应(Additional Responses)的完整实战解析 FastAPI 高级指南在 OpenAPI 中声明附加响应Additional Responses的完整实战解析【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi面向需要构建对外 API 的开发者本篇技术指南围绕FastAPI的path operation装饰器参数responses展开讲解如何在 OpenAPI 模式与自动生成的交互式 API 文档中为单个接口声明额外的状态码、媒体类型、Pydantic 模型与描述信息。阅读完成后你将能够为接口补充404、4XX、5XX等多类响应声明并让它们在/docs、/redoc与/openapi.json中完整呈现让同一个接口在文档层面同时表达 JSON 与图片等多种返回媒体类型掌握model键背后的 JSON Schema 生成与全局$ref引用机制并学会用**dict解包复用预定义响应。本篇以 docs/ja/docs/advanced/additional-responses.md 为骨架结合当前仓库源码与测试用例进行纵深剖析。什么是附加响应先理解声明与实现的分工在 FastAPI 中一个path operation天然拥有主响应由response_model与status_code决定。但真实的业务接口往往不止返回一种结果找不到资源返回404、权限不足返回403、资源被移动返回302……这些额外的响应在 OpenAPI 规范中被称为additional responses。原文指出这是一个比较高级的主题如果你刚开始使用 FastAPI可能并不需要它。它解决的问题非常具体——用追加的responses参数声明额外的状态码、媒体类型、描述等信息这些声明会被写进 OpenAPI schema从而展示在 API 文档中Swagger UI / ReDoc但请注意分工边界文档层面的声明不会帮你自动返回这些响应。你仍需要在代码里针对这些状态码直接返回一个指定了状态码与内容的Response例如JSONResponse、FileResponse。换句话说responses参数负责的是让文档与契约准确而真正发什么响应仍然由你的端点逻辑与返回的 Response 对象决定。温馨提示本文所有示例代码都来自仓库 docs_src/additional_responses 目录采用 Python 3.10 语法如bool | None联合类型写法。你可以用任意 ASGI 服务器例如uvicorn运行这些示例随后访问/docs查看文档、访问/openapi.json查看生成的 OpenAPI 原始结构。方式一通过model键声明携带 Pydantic 模型的附加响应基本写法给path operation 装饰器传入responses参数即可。它接收一个dict键key响应状态码例如200、404值value描述该响应的另一个dict。每个响应dict中可以包含一个model键其值是一个Pydantic 模型用法与顶层的response_model类似。FastAPI会从该模型生成 JSON Schema并把它放到 OpenAPI 中正确的位置。完整示例见 docs_src/additional_responses/tutorial001_py310.pyfrom fastapi import FastAPI from fastapi.responses import JSONResponse from pydantic import BaseModel class Item(BaseModel): id: str value: str class Message(BaseModel): message: str app FastAPI() app.get(/items/{item_id}, response_modelItem, responses{404: {model: Message}}) async def read_item(item_id: str): if item_id foo: return {id: foo, value: there goes my hero} return JSONResponse(status_code404, content{message: Item not found})代码要点拆解response_modelItem声明了200成功响应的结构responses{404: {model: Message}}为同一个端点追加声明404状态码的响应体将是Message模型的 JSON端点内部当item_id不为foo时直接返回JSONResponse(status_code404, content{message: Item not found})手动携带了状态码与内容。这一点是官方重点强调的由于404没有也不会有自动响应模型转换机制介入你必须像上面这样直接返回一个显式声明了status_code的JSONResponse才能让实际响应与文档声明保持一致。model键不是 OpenAPI 的一部分它是 FastAPI 的脚手架原文特别在备注中澄清了一个易混淆点model键并不是 OpenAPI 规范中的字段真正的 OpenAPI Responses Object 里并没有model这个东西。它的真实工作方式是FastAPI从responses的响应dict中取出model里的 Pydantic 模型由模型生成对应的JSON Schema再把 Schema 放到 OpenAPI 中正确的位置。这个正确位置依次是content键中值是一个dict其内以媒体类型如application/json为键值又是一个dict其内再放schema键值即为来自模型的 JSON Schema值得注意的是FastAPI 并不会把 Schema 内联复制到此处而是写入一条指向 OpenAPI 全局components.schemas的$ref引用。这样其他应用与客户端可以直接复用这些全局 JSON Schema从而支撑更好的代码生成工具与客户端 SDK。这一点在源码里可以找到明确的实现证据。在 fastapi/routing.py 的APIRoute构建过程中FastAPI 遍历route.responses从每个响应dict中取出model并为其创建序列化模式modeserialization的模型字段response_fields {} for additional_status_code, response in route.responses.items(): assert isinstance(response, dict), An additional response must be a dict model response.get(model) if model: assert is_body_allowed_for_status_code(additional_status_code), ( fStatus code {additional_status_code} must not have a response body ) response_name fResponse_{additional_status_code}_{route.unique_id} response_field create_model_field( nameresponse_name, type_model, modeserialization ) response_fields[additional_status_code] response_field随后在 OpenAPI 生成阶段fastapi/openapi/utils.py对每一条附加响应做深度拷贝、把内部脚手架字段model弹出process_response.pop(model, None)再把上一步构造好的字段 Schema 填入content[媒体类型].schemaif route.responses: operation_responses operation.setdefault(responses, {}) for ( additional_status_code, additional_response, ) in route.responses.items(): process_response copy.deepcopy(additional_response) process_response.pop(model, None) status_code_key str(additional_status_code).upper() if status_code_key DEFAULT: status_code_key default openapi_response operation_responses.setdefault(status_code_key, {}) assert isinstance(process_response, dict), ( An additional response must be a dict ) field route.response_fields.get(additional_status_code) ... media_type route_response_media_type or application/json也就是说model是给 FastAPI 用的脚手架它在最终输出的 OpenAPI 中被剥离并转化成规范化的schema$ref结构。观察生成的 OpenAPIresponses 与 components 两处联动对于上面的示例该path operation在 OpenAPI 中生成的responses如下{ responses: { 404: { description: Additional Response, content: { application/json: { schema: { $ref: #/components/schemas/Message } } } }, 200: { description: Successful Response, content: { application/json: { schema: { $ref: #/components/schemas/Item } } } }, 422: { description: Validation Error, content: { application/json: { schema: { $ref: #/components/schemas/HTTPValidationError } } } } } }可以看到除了我们手动声明的404FastAPI 还自动补上了200对应response_model与422自动校验错误。三者的schema全部是$ref引用形式。这些被引用的 Schema 则定义在 OpenAPI 顶层的components.schemas中{ components: { schemas: { Message: { title: Message, required: [ message ], type: object, properties: { message: { title: Message, type: string } } }, Item: { title: Item, required: [ id, value ], type: object, properties: { id: { title: Id, type: string }, value: { title: Value, type: string } } }, ValidationError: { title: ValidationError, required: [ loc, msg, type ], type: object, properties: { loc: { title: Location, type: array, items: { type: string } }, msg: { title: Message, type: string }, type: { title: Error Type, type: string } } }, HTTPValidationError: { title: HTTPValidationError, type: object, properties: { detail: { title: Detail, type: array, items: { $ref: #/components/schemas/ValidationError } } } } } } }注意这里的ValidationError/HTTPValidationError是 FastAPI 自动注册的默认 422 校验错误模型与Message、Item一同挂在全局components.schemas下。正因为所有模型都集中登记在全局 Schema 仓库中$ref才能被客户端工具反复复用。这些输出结构不是空口推测——仓库的回归测试 tests/test_tutorial/test_additional_responses/test_tutorial001.py 用snapshot精确断言了/items/{item_id}的openapi.json其中包括404的schema指向#/components/schemas/Message同一测试还验证了运行时的行为访问/items/foo返回200与ItemJSON访问/items/bar返回404与{message: Item not found}。方式二为同一个主响应追加额外的媒体类型responses参数同样可以用来给同一个状态码声明多个可返回的媒体类型。例如下面的示例完整代码见 docs_src/additional_responses/tutorial002_py310.py它声明该端点既可以返回 JSON 对象媒体类型application/json也可以返回一张 PNG 图片媒体类型image/pngfrom fastapi import FastAPI from fastapi.responses import FileResponse from pydantic import BaseModel class Item(BaseModel): id: str value: str app FastAPI() app.get( /items/{item_id}, response_modelItem, responses{ 200: { content: {image/png: {}}, description: Return the JSON item or an image., } }, ) async def read_item(item_id: str, img: bool | None None): if img: return FileResponse(image.png, media_typeimage/png) else: return {id: foo, value: there goes my hero}代码要点responses的键是200因为附加的image/png媒体类型属于成功响应本身content内声明媒体类型image/png其值为空对象{}表示不限 Schema同时给出自定义description运行时当查询参数img为真时直接返回FileResponse(image.png, media_typeimage/png)来发送真实图片文件。原文针对此场景给出两条重要的默认值语义备注除非你在responses参数里显式指定了别的媒体类型否则 FastAPI 默认认为响应与主响应类response class使用相同的媒体类型——默认情况下即application/json。如果你指定了一个media_type为None的自定义响应类那么凡是带有模型model键的附加响应FastAPI 都会回退使用application/json。这两点解释了为什么在示例一中我们没有写任何媒体类型、模型 Schema 却仍然被放进了application/json键下。对应地你可以参考 tests/test_tutorial/test_additional_responses/test_tutorial002.py 中对该接口 OpenAPI 结构与行为带img参数返回 200 图片、不带则返回 JSON的断言验证。方式三组合信息——response_model、status_code与responses协同工作responses并不是只能表达额外错误响应它还能与response_model、status_code一起把分散在多个位置的信息合并进同一条响应的 OpenAPI 描述中你可以照常声明response_model与默认状态码200需要的话也可以是任意状态码同时用responses为这条相同的响应补充 OpenAPI 层面的附加信息FastAPI 会完整保留responses中的附加信息并将其与模型的 JSON Schema 合并输出。经典例子见 docs_src/additional_responses/tutorial003_py310.py为404响应声明 Pydantic 模型Message并加上自定义description同时给response_model对应的200响应补充自定义examplefrom fastapi import FastAPI from fastapi.responses import JSONResponse from pydantic import BaseModel class Item(BaseModel): id: str value: str class Message(BaseModel): message: str app FastAPI() app.get( /items/{item_id}, response_modelItem, responses{ 404: {model: Message, description: The item was not found}, 200: { description: Item requested by ID, content: { application/json: { example: {id: bar, value: The bar tenders} } }, }, }, ) async def read_item(item_id: str): if item_id foo: return {id: foo, value: there goes my hero} else: return JSONResponse(status_code404, content{message: Item not found})这里有两类合并值得注意404的合并model提供 Schemadescription提供文字说明二者在 OpenAPI 中合成一个完整的404Response Object200的合并response_modelItem负责 Schema而responses中200的description与content.application.json.example属于纯 OpenAPI 层的补充字段会被保留并与既有 Schema 并存不会互相覆盖。这一切合并完成后都会进入 OpenAPI并显示在 API 文档中。原文展示的文档截图如下该截图在交互式文档中呈现了200与404两条带 Schema 与说明的响应条目。方式四用**dict解包组合预定义响应与自定义响应在实际项目中很多path operation会共享同一组错误响应例如大家都返回404、302、403。你当然可以把这些公共声明复制到每个接口上但更好的做法是先定义一个公用的响应dict再在每个接口处用 Python 的字典解包语法**将它合并进各自的responses。先看 Python 字典解包的基础语义原文示例old_dict { old key: old value, second old key: second old value, } new_dict {**old_dict, new key: new value}此时new_dict将包含old_dict的全部键值对外加新增的键值对{ old key: old value, second old key: second old value, new key: new value, }把这个技巧应用到接口上完整代码见 docs_src/additional_responses/tutorial004_py310.py先集中定义一组可复用的预定义响应再在每个path operation中叠加接口特有的自定义响应from fastapi import FastAPI from fastapi.responses import FileResponse from pydantic import BaseModel class Item(BaseModel): id: str value: str responses { 404: {description: Item not found}, 302: {description: The item was moved}, 403: {description: Not enough privileges}, } app FastAPI() app.get( /items/{item_id}, response_modelItem, responses{**responses, 200: {content: {image/png: {}}}}, ) async def read_item(item_id: str, img: bool | None None): if img: return FileResponse(image.png, media_typeimage/png) else: return {id: foo, value: there goes my hero}这里responses{**responses, 200: {...}}的含义是先展开全局预定义字典里的404、302、403三条声明再并入该接口独有的200image/png声明。如果有同名键后出现的键值会覆盖前者——因此你也可以利用这一点在个别接口重写默认的某条响应描述。仓库的 tests/test_tutorial/test_additional_responses/test_tutorial004.py 对该接口的 OpenAPI 输出做了快照断言可直接对照查看合并后的完整结构。纵深扩展从测试与源码看responses支持的更多键形态日语原文正文主要使用整数状态码作为键但responses 的键是状态码这一说法在当前仓库的实现中有着更丰富的内涵。通过 tests/test_additional_responses_router.py 可以确认responses字典的键还支持以下形态均会被如实写入 OpenAPI字符串形式的状态码例如400: {description: Error with str}状态码范围例如4XX、5XX大小写均可OpenAPI 支持1XX5XX通配范围写法如5XX: {model: ResponseModel}甚至可以为范围响应声明modeldefault键代表对所有未列出的状态码的兜底响应例如default: {model: ResponseModel}。从实现看这一逻辑位于 fastapi/openapi/utils.py生成时先把状态码键做字符串化与大写归一化再把DEFAULT规范化为小写的defaultstatus_code_key str(additional_status_code).upper() if status_code_key DEFAULT: status_code_key default另外responses并不只属于单个路由当前仓库允许在应用FastAPI 实例级别声明全局附加响应。回归测试 tests/test_additional_responses_union_duplicate_anyof.py 展示了FastAPI(responses{500: {model: ModelA | ModelB, ...}})的写法并验证当附加响应的model是Union类型A | B且多个路由都继承该响应时anyOf数组中的$ref条目不会因重复累积而膨胀。这说明 responses 声明体系经过了针对多路由、联合类型与示例字段组合的专门回归保障。反向的边界也有测试覆盖tests/test_additional_responses_bad.py 故意使用了responses{hello: {...}}这类非法状态码键并断言此时请求/openapi.json会抛出ValueError——换言之responses的键必须是 OpenAPI 认可的状态码数字、1XX–5XX范围或default之一。同时留意 fastapi/routing.py 中的一行校验凡是携带model的附加响应其状态码必须允许携带响应体。这与 HTTP 语义一致——像204 No Content、304 Not Modified这类本就无响应体的状态码是不能搭配model声明 body schema 的例如 204 必须返回空 body。需要了解的运行规则与注意事项综合原文说明与上述源码/测试证据使用附加响应时有几条关键规则需要牢记文档声明 ≠ 自动响应responses只影响 OpenAPI 与文档要让某个状态码真正发生必须在端点里直接返回对应Response如JSONResponse(status_code404, ...)、FileResponse(..., media_typeimage/png)否则会出现文档承诺了、运行却返回别的的契约错位。媒体类型有默认值未显式声明媒体类型的附加响应默认使用application/json若响应类本身media_type为None带model的附加响应同样回退到application/json。model键会被剥离它仅服务于 FastAPI 内部 Schema 生成最终 OpenAPI 中呈现的是content.media_type.schema处的$ref全局引用因此你的模型会进入components.schemas供客户端工具复用。无 body 状态码不可带模型204、304等状态码若声明model会在路由构建期被断言拦截。键必须是合法状态码数字状态码、1XX–5XX范围或default之外的值会导致 OpenAPI 生成失败抛出ValueError。信息可合并、可复用responses能跟response_model/status_code合并描述同一条响应也能借助**dict解包实现公共响应声明的批量复用。还能往响应里放什么Response Object 的能力边界responses字典里每个状态码对应的值本质上是一个 OpenAPI Response Object。因此你可以在其中直接放置符合该规范定义的各类元素而不止于model与descriptiondescription响应描述必填语义上的核心说明字段headers响应的自定义头信息声明content这里声明不同媒体类型及其对应的 JSON Schema、example/examples如方式三中在application/json里写examplelinks从该响应出发可到达的其他操作的关联描述。OpenAPI 规范中对Responses Object它承载各状态码到 Response Object 的映射键支持default、HTTP 状态码与1XX–5XX范围与Response Object描述单条响应的精确语义有权威定义建议以此为准核对字段取值在无法确认某字段是否受支持时最稳妥的方式是直接查看端点生成的/openapi.json输出进行验证。结合仓库做自测用 TestClient 锁定 OpenAPI 契约如果你希望像 FastAPI 官方那样为附加响应声明建立自动化保障仓库测试是现成的范本使用fastapi.testclient.TestClient请求/openapi.json再用快照断言工具比对整个响应结构。例如 tests/test_tutorial/test_additional_responses/test_tutorial001.py 的骨架就是from fastapi.testclient import TestClient from inline_snapshot import snapshot from docs_src.additional_responses.tutorial001_py310 import app client TestClient(app) def test_path_operation(): response client.get(/items/foo) assert response.status_code 200 assert response.json() {id: foo, value: there goes my hero} def test_path_operation_not_found(): response client.get(/items/bar) assert response.status_code 404 assert response.json() {message: Item not found} def test_openapi_schema(): response client.get(/openapi.json) assert response.status_code 200 assert response.json() snapshot({...}) # 精确比对 responses 与 components.schemas这种方式能同时锁定两件事运行时行为哪个 URL 返回什么状态码与 body和文档契约responses里每个状态码的 description / content / schema$ref。当你为项目声明了大量附加响应后这套测试能第一时间发现响应声明写错、模型结构被改、Schema 引用失效等问题。结语与参考索引通过responses参数FastAPI 让接口契约文档与代码实现可以精确对齐你既能声明任意数量的附加状态码与媒体类型又能借助 Pydanticmodel自动生成并全局复用 JSON Schema还能利用**dict解包让公共响应声明在多接口间保持单一来源。若要进一步深入推荐按如下顺序阅读当前仓库内的相关资料日语原文档docs/ja/docs/advanced/additional-responses.md英文对应文档docs/en/docs/advanced/additional-responses.md四个可运行示例docs_src/additional_responsestutorial001–tutorial004 均为py310版本OpenAPI 生成核心实现fastapi/openapi/utils.py其中附加响应处理集中在约 L473-L499路由层响应字段构建fastapi/routing.py契约/行为测试tests/test_tutorial/test_additional_responses、tests/test_additional_responses_router.py、tests/test_additional_responses_bad.py、tests/test_additional_responses_union_duplicate_anyof.py理解这一机制后你的 API 文档将不再只有200与自动的422而是一份能让前端、客户端生成器与协作者都看懂完整行为边界的精确契约。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表