
FastAPI OpenAPI Callbacks 实战把你的 API 回调外部 API的契约写进 Swagger 文档【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapiFastAPI 的 OpenAPI Callbacks 能力解决的是这样一类真实的接口协作问题你的 API 在业务处理中会主动去调用由 API 使用方外部开发者实现的另一个接口而你需要向对方精确说明你希望那个外部接口长什么样。本文基于仓库内 docs/es/docs/advanced/openapi-callbacks.md英文原版见 docs/en/docs/advanced/openapi-callbacks.md展开配合 docs_src/openapi_callbacks/tutorial001_py310.py 的可运行示例以及 fastapi/openapi/utils.py、fastapi/routing.py 的源码实现讲解如何用callbacks参数把外部回调接口的 path operation、请求体、响应体完整文档化让外部开发者在你的/docs页面上即可照着实现回调端点。读完后你将能独立复现发票通知这类回调场景并理解生成的 OpenAPI 中callbacks节点的结构与原理。Callback回调到底是什么当你的 API 主动去回叫别人在普通的 API 开发中通常是外部调用方请求你的接口而在 callback 场景里流程恰好多走了一步外部开发者编写的软件向你的 API发送请求你的 API 在完成内部处理后主动向一个由该外部开发者提供的外部 API发送请求这个再请求一次的动作就叫callback/ 回调这个外部 API 可能同样出自那位外部开发者之手。因此你面临的真实需求是文档化那个外部 API 应该长什么样——它应当提供什么path operation、期望接收什么请求体body、应当返回什么响应。这样外部开发者才能把你的回调端点实现得正确。一个最贴近直觉的例子是发票invoice系统你开发了一个允许创建发票的 API发票包含id、title可选、customer、total等字段。外部开发者通过 POST 在你的 API 中创建一张发票然后想象一下业务流程你的 API 会把发票发给该开发者的某个客户、向客户收款最后回叫外部开发者提供的外部 API把发票已支付这类事件通知发回去。这里发回通知的 POST 请求就是全文中反复讨论的 callback。不含 callback 文档的普通 FastAPI 应用先看还没有加入 callback 文档时这段 API 原本的样子。它只有一个接收Invoicebody 的path operation外加一个携带回调目标地址的 query 参数callback_urlfrom fastapi import APIRouter, FastAPI from pydantic import BaseModel, HttpUrl app FastAPI() class Invoice(BaseModel): id: str title: str | None None customer: str total: float app.post(/invoices/) def create_invoice(invoice: Invoice, callback_url: HttpUrl | None None): Create an invoice. This will (lets imagine) let the API user (some external developer) create an invoice. And this path operation will: * Send the invoice to the client. * Collect the money from the client. * Send a notification back to the API user (the external developer), as a callback. * At this point is that the API will somehow send a POST request to the external API with the notification of the invoice event (e.g. payment successful). # Send the invoice, collect the money, send the notification (the callback) return {msg: Invoice received}完整源码见 docs_src/openapi_callbacks/tutorial001_py310.py。其中有两点值得留意callback_url的类型是 Pydantic 的HttpUrlUrl类型之一配合| None None表示该参数可选。从 OpenAPI 输出可以看到 FastAPI 会为它生成format: uri、minLength: 1、maxLength: 2083的 schema见下文测试快照确保它必须是一个格式合法的 URL。函数体里的注释与 docstring 只是说明业务意图真正发送发票、收款、发送回调通知的逻辑都发生在你的应用内部函数本身只模拟收到发票这一结果。本文示例源码采用 Python 3.10 语法str | None仓库内对应文件名为tutorial001_py310.py测试也标记了needs_py310见 tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py。回调的执行代码与文档化代码是两个不同的东西真实的回调实现高度依赖你自己的业务不同应用差异很大。它可能真的只有一两行例如用 HTTPX 或 Requests 发出一个 POSTcallback_url https://example.com/api/v1/invoices/events/ httpx.post(callback_url, json{description: Invoice paid, paid: True})提示真正的回调本质上就是一个普通的 HTTP 请求自行实现时可以使用 HTTPX、Requests 等任意的 HTTP 客户端库。但回调里最重要的部分其实是确保外部开发者按你的 API 将要发送的数据格式把外部 API 实现正确。因此本文接下来的重点不是写回调本身而是编写用于文档化外部 API 的代码——它会在你的/docsSwagger UI中渲染出一段 Callbacks 区块让外部开发者知道该如何搭建那个将被你的 API 调用的外部接口。本文的示例只覆盖这段文档部分并没有真正发起回调。编写回调文档代码切换到外部开发者视角文档化回调的代码不会在你的应用里被执行它纯粹是用来描述外部 API 的蓝图。幸运的是你早已掌握用 FastAPI 为一个 API 自动生成文档的全部知识——现在只是把同一套知识借过来描述一个你自己不会实现、但会去调用的接口。写这段代码时最有效的技巧是想象自己就是那位外部开发者此刻正在实现外部 API而非你的 API。临时切换到这个视角参数放在哪里、body 用什么 Pydantic 模型、response 该返回什么都会一目了然。第 1 步创建专用于 callback 的APIRouter回调端点不能直接与你的业务路由混在一起而是先放进一个独立的APIRouter一个 router 可以容纳一个或多个回调from fastapi import APIRouter, FastAPI from pydantic import BaseModel, HttpUrl app FastAPI() invoices_callback_router APIRouter()第 2 步像写普通 path operation 一样定义回调端点回调的path operation看起来就是一条普通的 FastAPI 路径操作通常会包含两类声明它应当接收的请求体例如body: InvoiceEvent它应当返回的响应模型例如response_modelInvoiceEventReceived。配套的 Pydantic 模型以及回调端点代码如下class InvoiceEvent(BaseModel): description: str paid: bool class InvoiceEventReceived(BaseModel): ok: bool invoices_callback_router APIRouter() invoices_callback_router.post( {$callback_url}/invoices/{$request.body.id}, response_modelInvoiceEventReceived ) def invoice_notification(body: InvoiceEvent): pass与普通path operation相比它有两个主要区别不需要任何真实逻辑你的应用永远不会执行这段代码它只用来文档化外部 API所以函数体直接写pass即可路径中可以包含 OpenAPI 3 表达式key expression路径里可以引用发送到你的 API的原始请求中的参数和数据片段。提示文档化回调时应当声明它要接收的 body本例是InvoiceEvent也可以声明它应返回的 response本例通过response_modelInvoiceEventReceived声明。第 3 步理解回调路径里的 OpenAPI 表达式上面的回调路径写成了字符串{$callback_url}/invoices/{$request.body.id}这里{$callback_url}与{$request.body.id}就是 OpenAPI 3 中定义的 key expression前者取外部开发者在调用你的 API 时通过 query 参数callback_url传入的值后者取他发送给你的 JSON body 中的id字段。把它串起来走一遍完整时序就清楚了。假设外部开发者向你的 API 发起请求https://yourapi.com/invoices/?callback_urlhttps://www.external.org/events请求体为{ id: 2expen51ve, customer: Mr. Richie Rich, total: 9999 }你的 API 处理完发票后在某个时机会向callback_url指向的外部 API 发出回调请求https://www.external.org/events/invoices/2expen51ve回调请求体大致如下{ description: Payment celebration, paid: true }而你的 API 期望这个外部 API 返回的 JSON 形如{ ok: true }注意观察最终回调 URL 中同时出现了两个动态来源——query 参数callback_url的值https://www.external.org/events以及发票 JSON body 里的id2expen51ve。这正是 key expression 的用途它让 OpenAPI 能够表达回调地址完全由调用方在运行时提供的动态契约。第 4 步把回调 router 挂到你的 path operation 上回调端点定义好之后用你的 path operation 装饰器中的callbacks参数把它们挂接上去。注意传入的是该 router 的属性.routes而不是 router 本身app.post(/invoices/, callbacksinvoices_callback_router.routes) def create_invoice(invoice: Invoice, callback_url: HttpUrl | None None): ...invoices_callback_router.routes就是回调 router 中已注册路由APIRoute的列表FastAPI 会遍历这些路由来生成 OpenAPI 回调文档。从源码看装饰器最终会把该列表原样保存到路由对象上fastapi/routing.py 中的route.callbacks callbacks并在生成 OpenAPI 时消费它。检查/docs上的 Callbacks 文档现在启动应用并打开http://127.0.0.1:8000/docs你会在对应path operation的文档中看到新增的 Callbacks 区块里面清晰地展示了外部 API 应当如何实现外部开发者只要浏览你的接口文档就能照此实现接收回调通知的POST /events/invoices/{invoice_id}端点。你的 API 与外部 API 的协作契约因此变成了可交互、可阅读、可校验的文档而不是靠口口相传。源码视角FastAPI 如何把callbacks变成 OpenAPI 文档理解了用法之后再深入源码看 FastAPI 究竟做了什么。路由层回调以路由列表形式挂载在 fastapi/routing.py 中callbacks: list[BaseRoute] | None是所有核心路由方法api_route/get/post等的公共参数在装饰器内部它被直接写入route.callbacks。也就是说回调在 FastAPI 内部就是一组与普通路由同构的APIRoute只是它们的路径键是 key expression。OpenAPI 生成层递归生成每个回调的 path 文档真正把回调写进openapi.json的代码位于 fastapi/openapi/utils.py。当某个 operation 带有route.callbacks时FastAPI 会为回调创建一个以回调端点函数名为键的callbacks字典对route.callbacks中的每一个APIRoute复用生成普通路径操作的同一套get_openapi_path逻辑递归处理得到该回调的 path、参数、请求体、响应体等在生成的 operation 里写入operation[callbacks]。因此回调端点拥有的 body 校验、response_model、422 错误响应等一切能力都与普通端点完全一致唯一的差异只是它们挂在callbacks键下、路径表现为{$callback_url}/...这样的运行时表达式。测试快照openapi.json里真实的 callbacks 节点仓库为本文示例编写了完整的 OpenAPI 快照测试 tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py。测试同时验证了三件事向/invoices/POST 发票能正常返回{msg: Invoice received}invoice_notification这个仅为覆盖测试而存在的外部端点能被直接调用mod.invoice_notification({})以及最重要的——/openapi.json输出完全符合预期快照。其中callbacks节点的结构为{ paths: { /invoices/: { post: { parameters: [ { required: false, schema: { anyOf: [ { type: string, format: uri, minLength: 1, maxLength: 2083 }, {type: null} ], title: Callback Url }, name: callback_url, in: query } ], requestBody: { content: { application/json: { schema: {$ref: #/components/schemas/Invoice} } }, required: true }, callbacks: { invoice_notification: { {$callback_url}/invoices/{$request.body.id}: { post: { summary: Invoice Notification, operationId: invoice_notification__callback_url__invoices___request_body_id__post, requestBody: { required: true, content: { application/json: { schema: {$ref: #/components/schemas/InvoiceEvent} } } }, responses: { 200: { description: Successful Response, content: { application/json: { schema: {$ref: #/components/schemas/InvoiceEventReceived} } } }, 422: { description: Validation Error } } } } } } } } } }从快照可以印证源码中的实现细节callbacks的键是回调端点函数名invoice_notification其下以回调路径字符串{$callback_url}/invoices/{$request.body.id}为键值为该方法对应的 OpenAPI operation 对象callback_url参数因为使用了 Pydantic 的HttpUrl在 schema 中表现为format: uri且长度限制为 12083Invoice、InvoiceEvent、InvoiceEventReceived三个模型都被提取进components/schemas与普通端点的模型处理路径完全一致——这也再次证明回调端点在 OpenAPI 生成上就是一条普通端点只是被放进了callbacks容器并用 key expression 作为路径。小结与适用边界概括起来FastAPI 的 OpenAPI Callbacks 特性让你可以用一个独立的APIRouter描述你的 API 会去调用的外部接口body、response 均照常声明函数体可仅为pass通过callbacksrouter.routes把它挂到触发回调的那个业务path operation上在/docs中为外部开发者呈现一份自动生成的、可读的 Callbacks 契约配合 OpenAPI 3 的 key expression 表达回调地址来自调用方入参这一动态语义。需要注意的是边界callbacks只负责文档化外部 API并不会替你发出任何请求——真实回调仍由你的业务代码用 HTTPX、Requests 等客户端完成callback_url的校验、回调路径表达式的取值等行为也都由你在业务侧自行落实示例函数内的注释即示意了这一点。当你需要设计我的 API 会回调使用者的开放平台类接口时这一机制能把最难讲清楚的部分——外部回调端点的数据契约——用 FastAPI 原生的方式讲清楚。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考