FastAPI 扩展 OpenAPI Schema:用 get_openapi 自定义文档与厂商扩展
FastAPI 会根据路由自动生成 OpenAPI Schema(即 /openapi.json 返回的内容),并把它作为 Swagger UI 与 ReDoc 文档的数据源。本文以仓库文档 docs/fr/docs/how-to/extending-openapi.md 为主线,结合 FastAPI 源码讲解 Schema 的默认生成流程,并给出一个完整的"自定义 OpenAPI"实战方案:通过覆盖 app.openapi 并复用 fastapi.openapi.utils.get_openapi,在保留全部自动生成结果的基础上,为文档注入自定义的 ReDoc 厂商扩展(如自定义 Logo)。读完你将能独立实现任意 OpenAPI 字段的增改,且不破坏原有的缓存与性能特性。
默认的 OpenAPI 生成流程
.openapi() 方法与 /openapi.json 端点
每个 FastAPI 应用实例都拥有一个 .openapi() 方法,其职责是返回完整的 OpenAPI Schema 字典。在应用对象创建时,FastAPI 会注册一个指向 /openapi.json(或你在 FastAPI(openapi_url=...) 中指定的任意 URL)的路径操作,它仅仅把 .openapi() 的结果包装成 JSON 响应返回。
在源码 fastapi/applications.py 的 setup() 中可以确认这条链路:
if self.openapi_url:
async def openapi(req: Request) -> JSONResponse:
root_path = req.scope.get("root_path", "").rstrip("/")
schema = self.openapi()
...
return JSONResponse(schema)
self.add_route(self.openapi_url, openapi, include_in_schema=False)
注意两点:
- 端点路由是在运行时通过
self.openapi()属性动态取用的,因此稍后把app.openapi替换成自定义函数,这一注册行为不受影响; - 只有当
openapi_url非空时才会注册(例如FastAPI(openapi_url=None)可以整体禁用该端点)。
.openapi_schema 缓存与默认实现
默认情况下,.openapi() 方法的第一步是检查实例属性 .openapi_schema:如果已有内容就直接返回;否则调用工具函数 fastapi.openapi.utils.get_openapi 现场生成。生成结果会写回 self.openapi_schema,实现"只生成一次、后续直接读取缓存"的效果。self.openapi_schema 在 fastapi/applications.py 中被初始化为 None。
源码中的默认实现(见 fastapi/applications.py)实际上还加入了一层路由变更失效机制:
def openapi(self) -> dict[str, Any]:
routes_version = self.router._get_routes_version()
if not self.openapi_schema or self._openapi_routes_version != routes_version:
self.openapi_schema = get_openapi(
title=self.title,
version=self.version,
openapi_version=self.openapi_version,
summary=self.summary,
description=self.description,
terms_of_service=self.terms_of_service,
contact=self.contact,
license_info=self.license_info,
routes=self.routes,
webhooks=self.webhooks.routes,
tags=self.openapi_tags,
servers=self.servers,
separate_input_output_schemas=self.separate_input_output_schemas,
external_docs=self.openapi_external_docs,
)
self._openapi_routes_version = routes_version
return self.openapi_schema
也就是说:内置实现除了缓存之外,还会在路由树版本变化(例如应用启动后新增了路由)时自动重新生成 Schema。
get_openapi() 的参数
工具函数 get_openapi() 定义于 fastapi/openapi/utils.py。官方文档列出了它的核心参数,实际签名还包含更多可选参数,统一整理如下:
| 参数 | 含义 | 默认值 |
|---|---|---|
title |
OpenAPI 标题,会显示在文档页 | 必填 |
version |
API 版本号,例如 2.5.0 |
必填 |
openapi_version |
使用的 OpenAPI 规范版本 | 3.1.0(最新版) |
summary |
API 的简短摘要 | None |
description |
API 详细描述,支持 Markdown,会渲染在文档页 | None |
terms_of_service |
服务条款 URL | None |
contact |
联系信息对象(name/url/email) |
None |
license_info |
许可证信息对象(name/identifier 等) |
None |
routes |
应用路由(取自 app.routes) |
必填 |
webhooks |
Webhook 路由列表 | None |
tags |
OpenAPI 顶层标签描述列表 | None |
servers |
OpenAPI servers 数组 |
None |
separate_input_output_schemas |
是否为输入/输出分别生成独立 Schema | True |
external_docs |
externalDocs 对象 |
None |
技术细节:
app.routes是一个更底层的路由树,其中可能包含 FastAPI 为被 include 的路由器内部使用的候选路由,而不仅仅是最终的APIRoute对象。尽管如此,你仍可以直接把app.routes传给get_openapi(),FastAPI 会遍历这棵路由树,收集其中真实生效的路径操作(参见 fastapi/openapi/utils.py 中对routes展开与字段收集的实现)。
版本提示:
summary参数属于 OpenAPI 3.1.0 及以上规范的能力,FastAPI 0.99.0 及以上版本才支持该参数;低于此版本时应忽略它。
覆盖默认值:给 ReDoc 注入自定义 Logo
理解了默认流程后,就能采用"复用同一工具函数生成 Schema,再逐项覆盖需要修改的部分"这一通用套路。下面以最典型的场景——为 ReDoc 添加自定义 Logo(x-logo 厂商扩展,vendor extension)为例,完整演示。
第 1 步:编写常规 FastAPI 应用
先按平常方式写一个应用,本示例存放于 docs_src/extending_openapi/tutorial001_py310.py:
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
第 2 步:用工具函数生成 Schema
接着定义一个 custom_openapi() 函数,在函数体内调用同一个 get_openapi() 来生成 Schema。这里可以传入你想要的 title、version、summary、description,并传入 routes=app.routes:
def custom_openapi():
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
summary="This is a very custom OpenAPI schema",
description="Here's a longer description of the custom **OpenAPI** schema",
routes=app.routes,
)
...
借助这一步,你可以"免费"获得原本自动生成的全部内容:paths、components/schemas、securitySchemes、422 校验错误响应定义等,因为它们统统由 get_openapi() 依据路由推导而来。
第 3 步:修改生成的 Schema
拿到 Schema 字典后,就可以像操作普通 dict 一样加入自定义内容。ReDoc 的 x-logo 扩展放在 Schema 顶层的 info 对象里,指向一张 Logo 图片 URL:
openapi_schema["info"]["x-logo"] = {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
x- 开头的字段是 OpenAPI 规范的"厂商扩展"(Vendor Extensions)机制,工具链遇到不认识但以 x- 开头的键都会保留并透传,因此这是在不破坏规范的前提下注入自定义元数据的标准做法。
第 4 步:把 Schema 存入缓存
把修改后的 Schema 写回 app.openapi_schema 属性作为"缓存"。这样应用不必在每次用户打开 API 文档时都重新生成一次 Schema——它只被生成一次,后续所有请求都直接复用这份缓存结果:
app.openapi_schema = openapi_schema
return app.openapi_schema
第 5 步:覆盖 .openapi() 方法
最后把应用默认的 .openapi() 方法替换为你的新函数:
app.openapi = custom_openapi
由于第 4 步已经把结果写回缓存,第 5 步覆盖之后,无论是 /openapi.json 还是文档页内部的 Schema 加载,都会命中 custom_openapi() 的逻辑:首次调用生成并缓存,此后直接返回缓存。缓存命中分支同样应在函数开头显式处理(下节给出完整代码)。
完整代码与效果验证
把上述五步串起来(含缓存判空逻辑),得到完整实现如下:
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
summary="This is a very custom OpenAPI schema",
description="Here's a longer description of the custom **OpenAPI** schema",
routes=app.routes,
)
openapi_schema["info"]["x-logo"] = {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
运行应用后访问 http://127.0.0.1:8000/redoc,可以看到文档页顶部使用了自定义 Logo(本例为 FastAPI 自身的 Logo),同时右侧仍完整展示 /items/ 的 GET 路径操作及其响应模型,说明自定义 Schema 并没有影响自动推导的路由信息:
同样的改动也作用于 http://127.0.0.1:8000/docs(Swagger UI)以及直接访问 http://127.0.0.1:8000/openapi.json 返回的原始 JSON——三者共享同一份 app.openapi() 结果。你也可以在浏览器中直接查看 /openapi.json,确认 info 对象中已多出 x-logo 字段,以及 title、summary、description 均被替换为自定义值。
注意事项与更深入的自定义方向
- 性能与缓存语义:内置
.openapi()的缓存还带有"路由版本变化即失效重算"的逻辑;而本教程的自定义函数中,缓存命中后直接返回,Schema 是静态固定的。如果你在应用运行期动态追加路由且希望其进入 Schema,需要自行设计失效策略(例如监听路由变化后清空app.openapi_schema)。 summary的可用前提:只有 FastAPI 0.99.0+(对应 OpenAPI 3.1.0)才支持summary参数,老旧工具链可能不识别该字段。- 其他相关主题:若需要"仅在特定环境暴露
/docs与/openapi.json",可参考 conditional-openapi.md;若想定制 Swagger UI 本身的参数(如默认展开深度),见 configure-swagger-ui.md;文档页面 JS/CSS 等静态资源替换则见 custom-docs-ui-assets.md。
这套"复用 get_openapi() + 覆盖 app.openapi + 用 app.openapi_schema 做缓存"的三段式写法,是扩展 FastAPI 生成式 Schema 的标准范式。基于它,你不仅可以添加 x-logo,还能按需改写 info、tags、servers,甚至逐路径调整 paths 与 components,实现完全贴合业务的 API 文档输出。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
