news 2026/9/7 10:08:23

FastAPI 中的 OpenAPI Callbacks:用 `callbacks` 参数自动文档化外部回调 API

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI 中的 OpenAPI Callbacks:用 `callbacks` 参数自动文档化外部回调 API

FastAPI 中的 OpenAPI Callbacks:用callbacks参数自动文档化外部回调 API

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

本文围绕 FastAPI 官方教程docs/de/docs/advanced/openapi-callbacks.md(OpenAPI-Callbacks)展开:当你的 API 在处理请求后会反过来调用外部开发者的 API(即"回调")时,如何用一个APIRouter加上路径操作装饰器的callbacks参数,把这条"回调契约"完整写入 OpenAPI 规范,并自动展示在 Swagger UI 的 Callbacks 标签页中。读完后,你将能够:定义带回调文档的 FastAPI 应用、使用 OpenAPI 3 表达式让回调路径动态引用原始请求中的参数与 Body 字段,并在/docs中验证外部开发者应实现的接口形态。

什么是 OpenAPI Callbacks:先理解"回调"场景

你可以构建一种 API:其中某个路径操作(path operation)在处理过程中会触发对外部 API的请求——这个外部 API 由别人创建(通常正是那个正在使用你 API 的开发者)。

这个过程被称为Callback(回调):外部开发者编写的软件先向你的 API 发送请求,随后你的 API 再"回拨"(calls back),向该外部 API 发送一个请求。

此时你往往希望文档化这条外部 API 应有的样子

  • 它应包含哪个路径操作(哪个方法、哪个路径);
  • 它应接收什么样的请求 Body;
  • 它应返回什么样的响应。

FastAPI 借助 OpenAPI 规范原生的callbacks字段,让你复用"写 FastAPI 路径操作"的全部经验来描述这条外部 API——包括参数声明、Pydantic 请求体模型和response_model

示例场景:一个带回调的发票应用

用一个具体例子贯穿全文:假设你开发了一个发票创建应用

每张发票包含:idtitle(可选)、customertotal

你的 API 使用者(一个外部开发者)通过 POST 请求在你的 API 中创建一张发票。随后你的 API(假设性地)会:

  • 把发票发送给该外部开发者的某个客户;
  • 收齐款项;
  • 向 API 使用者(外部开发者)发回一条通知——
    • 这一步是通过你的 API 向外部开发者提供的外部 API 发送一个 POST 请求实现的,这就是"回调"。

下面的教程代码位于 docs_src/openapi_callbacks/tutorial001_py310.py,完整文件仅 51 行,本文会逐段讲解。

普通的 FastAPI 应用部分

先看在加入回调之前,常规 API 应用长什么样。它有一个路径操作:接收Invoice请求体,并带一个携带回调 URL 的查询参数callback_url

from 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/", callbacks=invoices_callback_router.routes) def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None): """ Create an invoice. ... """ # Send the invoice, collect the money, send the notification (the callback) return {"msg": "Invoice received"}

这里有一个值得注意的细节:callback_url查询参数使用了 Pydantic 的HttpUrl类型(见 docs_src/openapi_callbacks/tutorial001_py310.py)。从生成的 OpenAPI 测试快照可以看到,该参数被渲染为format: "uri"minLength: 1maxLength: 2083的字符串 schema,且因HttpUrl | None而变为可选(required: False),见 tests/test_sub_callbacks.py。

上面代码中唯一"新"的东西是传给路径操作装饰器的参数callbacks=invoices_callback_router.routes,下面详解。

文档化回调本身:真正的回调代码有多简单

实际的回调实现代码强烈依赖于你自己的业务,因应用而异,可能只是短短一两行,例如:

callback_url = "https://example.com/api/v1/invoices/events/" httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})

但回调中最关键的部分,是确保你的 API 使用者(外部开发者)正确实现了那条外部 API——按照你的 API 将在回调请求体中发送的数据格式来实现。本教程示例不实现回调本身(它可能只有一行代码),只演示文档化的部分。

提示:实际的回调本质上就是一个 HTTP 请求。实现时可以使用httpxrequests等任意 HTTP 客户端库。

这一点也被 FastAPI 源码的官方文档字符串明确确认:callbacks参数的 Doc 写着 "List ofpath operationsthat will be used as OpenAPI callbacks.This is only for OpenAPI documentation, the callbacks won't be used directly.It will be added to the generated OpenAPI (e.g. visible at/docs)"(见 fastapi/applications.py 中include_routercallbacks参数说明)。也就是说,回调路由永远不会被你的应用执行,它们只用于生成 OpenAPI 文档。

编写回调文档代码:把自己想象成"外部开发者"

用来文档化回调的代码不会在你的应用中执行,你只是需要它来描述那条外部 API 应长什么样。但你已经知道如何用 FastAPI 轻松创建自动文档——于是用同样的方法,把外部 API 应实现的路径操作写出来即可。

提示:编写回调文档代码时,不妨想象自己就是那个外部开发者,此刻正在实现的不是你的 API,而是外部 API。暂时切换到这个视角后,参数放在哪里、Body 用哪个 Pydantic 模型、Response 是什么,都会变得直观。

第 1 步:创建一个回调APIRouter

先新建一个APIRouter,用来装一个或多个回调:

from fastapi import APIRouter, FastAPI invoices_callback_router = APIRouter()

见 docs_src/openapi_callbacks/tutorial001_py310.py。

第 2 步:创建回调路径操作

使用上面创建的APIRouter来定义回调路径操作,它看起来就像一条普通的 FastAPI路径操作

  • 声明它应接收的 Body,如body: InvoiceEvent
  • 可以声明它应返回的响应,如response_model=InvoiceEventReceived
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_model=InvoiceEventReceived ) def invoice_notification(body: InvoiceEvent): pass

见 docs_src/openapi_callbacks/tutorial001_py310.py。

与普通路径操作相比,有两个主要区别:

  1. 函数体不需要真实代码:因为你的应用永远不会调用它,它只用于文档化外部 API,所以函数体可以只有pass
  2. 路径可以包含 OpenAPI 3 表达式:可以引用发送到你 API 的原始请求中的变量(参数和请求体部分)。

回调路径表达式(OpenAPI 3 expression)

回调路径可以是一个OpenAPI 3 表达式str字符串),其中可以内嵌对原始请求内容的引用。本例中是:

"{$callback_url}/invoices/{$request.body.id}"

表达式的语义:{$callback_url}取自原始请求的查询参数callback_url{$request.body.id}取自原始请求 JSON Body 中的id字段。

完整走一遍数据流:

外部开发者向你的 API发送请求:

https://yourapi.com/invoices/?callback_url=https://www.external.org/events

JSON Body 为:

{ "id": "2expen51ve", "customer": "Mr. Richie Rich", "total": "9999" }

你的 API 处理完发票后,在某个时刻会向callback_url(即外部 API)发送回调请求:

https://www.external.org/events/invoices/2expen51ve

回调的 JSON Body 大致为:

{ "description": "Payment celebration", "paid": true }

并期望从该外部 API收到如下 JSON 响应:

{ "ok": true }

注意:最终使用的回调 URL 同时包含了两处信息——作为查询参数传入的callback_urlhttps://www.external.org/events)和 JSON Body 中的发票id2expen51ve)。这正是 OpenAPI 表达式相对静态路径的价值所在。

第 3 步:把回调 Router 挂到装饰器上

此时,所需的回调路径操作(即外部开发者应在外部 API中实现的接口)已经放在回调 Router 里。现在在你的API 的路径操作装饰器上,通过callbacks参数传入该回调 Router 的.routes属性:

@app.post("/invoices/", callbacks=invoices_callback_router.routes) def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None): ...

见 docs_src/openapi_callbacks/tutorial001_py310.py。

提示:传给callbacks=不是 Router 本身invoices_callback_router),而是它的.routes属性,即invoices_callback_router.routes。FastAPI 会用这些路由来生成回调的 OpenAPI 文档。

从源码看,callbacks的类型就是list[BaseRoute] | None,在路由初始化时被原样存储到路由对象上(见 fastapi/routing.py 中_populate_api_route_statecallbacks参数与 fastapi/routing.py 的route.callbacks = callbacks)。

回调如何被写入 OpenAPI:源码视角

OpenAPI 生成时,FastAPI 遍历route.callbacks,对每条APIRoute递归生成完整的 operation,并以回调函数名为键组织成callbacks字典挂到对应 operation 下:

if route.callbacks: callbacks = {} for callback in route.callbacks: if isinstance(callback, routing.APIRoute): ( cb_path, cb_security_schemes, cb_definitions, ) = get_openapi_path(route=..., ...) callbacks[callback.name] = {callback.path: cb_path} operation["callbacks"] = callbacks

见 fastapi/openapi/utils.py。这解释了两件事:

  • 回调 operation 会像普通操作一样生成operationIdrequestBodyresponses,且回调中引用的 Pydantic 模型(InvoiceEventInvoiceEventReceived)也会进入components/schemas(该处同时收集回调路由的模型定义,见 fastapi/openapi/utils.py 的get_fields_from_routes(api_route.callbacks));
  • 回调的键是回调函数的name(如invoice_notification),值是以表达式路径字符串{$callback_url}/invoices/{$request.body.id},注意不是编译后的正则,而是你写的原始字符串)为键的 PathItem。

测试快照完整印证了这段实现:在 tests/test_sub_callbacks.py 中,/invoices/的 POST operation 下生成了"callbacks"对象,包含event_callback(GET,表达式{$callback_url}/events/{$request.body.title})与invoice_notification(POST,表达式{$callback_url}/invoices/{$request.body.id})两个回调,且requestBody分别引用#/components/schemas/Event#/components/schemas/InvoiceEvent

进阶:include_router也可以附加回调

除了逐条挂在路径操作装饰器上,app.include_router(...)APIRouter本身也接受callbacks参数,可为 Router 内的所有路径操作附加回调文档(源码 Doc 说明为 "OpenAPI callbacks that should apply to allpath operationsin this router",见 fastapi/routing.py 中APIRouter.include_router的参数定义)。上文的 tests/test_sub_callbacks.py 正是这个用法:POST /invoices/自带callbacks=invoices_callback_router.routes,而app.include_router(subrouter, callbacks=events_callback_router.routes)又追加了一个event_callback——两个来源的回调最终合并出现在同一 operation 的callbacks中。从源码结构看,_RouterIncludeContext在 include 链路上会持续合并父级与子级的 callbacks(见 fastapi/routing.py 的callbacks: list[BaseRoute]字段及 fastapi/routing.py 的合并逻辑)。

/docs中验证

启动应用并访问http://127.0.0.1:8000/docs,你会看到 Swagger UI 中该路径操作多出一个Callbacks标签页,展示外部 API应有的样子:

从截图中可以看到:

  • Callbacks 区块以回调函数名invoice_notification为标题;
  • 方法为POST,路径显示为表达式原文{$callback_url}/invoices/{$request.body.id}
  • Request body标记为 required,内容为InvoiceEvent的示例值(description: "string"paid: true);
  • Responses列出 200 等状态码。

这正是给外部开发者看的"契约":照着这个结构实现一个接收InvoiceEvent并返回InvoiceEventReceived的接口,即可正确接收你的回调。

小结:关键要点回顾

要点说明依据
callbacks参数传入list[BaseRoute],通常为某callback_router.routes,而非 Router 本身fastapi/routing.py、docs_src/openapi_callbacks/tutorial001_py310.py
回调只为文档回调路径操作不会被应用执行,仅用于生成 OpenAPI 文档,函数体可passfastapi/applications.py
表达式路径回调路径可用{$参数名}{$request.body.字段}引用原始请求fastapi/openapi/utils.py、tests/test_sub_callbacks.py
挂接位置路径操作装饰器(@app.post(..., callbacks=...))与include_router(..., callbacks=...)均可fastapi/routing.py
callback_url类型用 PydanticHttpUrl声明查询参数,OpenAPI 中渲染为format: uri且可选tests/test_sub_callbacks.py
验证方式访问/docs查看 Callbacks 标签页,或对比/openapi.json中 operation 的callbacks字段tests/test_sub_callbacks.py

参考文件:教程文档 docs/de/docs/advanced/openapi-callbacks.md、示例代码 docs_src/openapi_callbacks/tutorial001_py310.py、核心实现 fastapi/openapi/utils.py 与 fastapi/routing.py、测试 tests/test_sub_callbacks.py。

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 10:08:11

Astra多模态智能体实战:将Fortnite变成文字冒险游戏的技术解析

你见过把Fortnite这种满屏枪火、盖楼、跑毒的大逃杀游戏,硬生生变成一局只能靠敲字推进的老式文字冒险吗?沃顿商学院教授Ethan Mollick还真干过这事——他利用Astra多模态智能体,让玩家用自然语言“玩”Fortnite。Astra会盯着游戏画面&#x…

作者头像 李华
网站建设 2026/9/7 10:07:51

SDD规格驱动开发实战:用清晰需求让AI编程更可控

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 10:07:11

高职单招面试全攻略:从准备到答题的实战技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 10:05:10

RAG企业知识库实战教程:从原理到代码全流程

今年在做企业知识库项目时,团队遇到一个非常典型的问题:资料文件堆积了几十个 GB,业务人员想找一份历史合同的关键条款,得翻半天共享盘;想问“去年 Q3 的客诉处理周期是多少”,运维、研发、销售各说各话。传…

作者头像 李华