FastAPI 自定义扩展 OpenAPI schema:用 get_openapi 覆盖默认生成逻辑
导读:FastAPI 会根据应用的 path operation 自动生成一份 OpenAPI 3.1.0 schema,并暴露为
/openapi.json,供 Swagger UI、ReDoc 等交互式文档消费。本文以在文档页中注入自定义 Logo(ReDoc 的x-logo扩展)为主线,讲解 OpenAPI schema 的默认生成流程、get_openapi()核心参数,以及通过覆写app.openapi方法实现对生成结果的深度定制;读完后你将掌握一套可复用的 schema 定制模板,并能看懂其背后的缓存与路由采集原理。文中示例源码位于 docs_src/extending_openapi/tutorial001_py310.py,官方教程正文在 docs/es/docs/how-to/extending-openapi.md。
默认情况下 schema 是如何生成的
要对生成结果做定制,首先必须理解默认的"正常流程"。整个过程由 FastAPI 应用实例上的三个角色配合完成,在 fastapi/applications.py 中都能找到对应实现:
.openapi()方法:返回整个 OpenAPI schema(一个 dict)。.openapi_schema属性:schema 的内存级缓存。默认.openapi()会先检查该属性是否已有内容,有则直接返回,避免每次请求都重新生成。fastapi.openapi.utils.get_openapi工具函数:当缓存为空时,真正负责"从零生成" schema。
同时在应用对象创建阶段,FastAPI 会注册一条指向 /openapi.json(或你在 openapi_url 中配置的任意路径)的 path operation。这条路由只是把 .openapi() 的返回值包装成 JSON 响应返回。在 setup() 方法中可以看到这段逻辑:self.add_route(self.openapi_url, openapi, include_in_schema=False),其中 openapi 是一个异步端点,内部调用 self.openapi() 并返回 JSONResponse;若配置了 root_path 且 root_path_in_servers=True,还会在 servers 顶部注入服务地址。
而 .openapi() 的默认实现(fastapi/applications.py)比文档描述的"查缓存"多了一层细节:它会用 self.router._get_routes_version() 记录路由版本号,当路由树发生变化(例如新注册了路由)而版本号与缓存生成时不一致时,会自动失效缓存并重新生成。也就是说,默认实现本身已具备一定的缓存新鲜度保障——这一点在覆写方法时值得留意(见下文"缓存与路由变更")。
get_openapi() 的核心参数
当缓存未命中时,生成工作交给 get_openapi()。该函数位于 fastapi/openapi/utils.py,文档明确列出的参数有:
title:OpenAPI 的标题,会显示在文档页上。version:你的 API 版本,例如2.5.0。openapi_version:使用的 OpenAPI 规范版本,默认最新版3.1.0。summary:API 的简短摘要。该参数在 OpenAPI 3.1.0 及以上才有,FastAPI 0.99.0 及以上版本支持。description:API 描述,支持 Markdown,会渲染在文档页中。routes:取自app.routes的应用路由。FastAPI 依据它收集所有已注册 path operation,包括通过include_router()挂载进来的路由。
技术细节:routes 到底是什么
从源码注释与函数签名(routes: Sequence[BaseRoute | routing.RouteContext])可以确认一个容易被误解的点:app.routes 是一个低层路由树,它内部不仅包含最终的 APIRoute 对象,还可能含有 FastAPI 处理"被 include 的 router"时使用的候选路由节点。不过你依然可以直接把 app.routes 传给 get_openapi()——FastAPI 内部通过 routing.iter_route_contexts() 遍历这棵树并筛选出"有效的 path operation"(见 get_openapi 实现 中对 _get_api_route_for_openapi 的过滤)。
签名中还有哪些可覆盖参数
对照 utils.py 的函数签名可以发现,除了文档列出的 6 个参数,get_openapi() 还接受 webhooks、tags、servers、terms_of_service、contact、license_info、separate_input_output_schemas、external_docs 等可选参数。它们分别对应 OpenAPI 顶层对象中的 webhooks、tags、servers、info.termsOfService、info.contact、info.license 等字段,并支持 info 中嵌入这些扩展信息。这意味着即使不逐字段手改 dict,也能通过参数实现相当大范围的定制。
覆写默认值:给 ReDoc 加一个自定义 Logo
文档给出的经典案例是:使用同一个工具函数 get_openapi() 生成 schema,再覆写需要修改的部分——这里以加入 ReDoc 的厂商扩展 x-logo(自定义 Logo)为例。整个实现分五步。
第 1 步:正常编写 FastAPI 应用
先像平常一样定义一个 FastAPI 实例和一个路径操作:
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。这一步复用了 FastAPI 默认的生成能力,而不是手写整份 JSON:
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
第 3 步:修改 schema 对象
因为 get_openapi() 返回的是普通 Python dict,生成后可以像操作任何字典一样直接修改。这里向 info 对象中追加 ReDoc 的 x-logo 扩展:
openapi_schema["info"]["x-logo"] = {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
info 中的 title、version、summary、description 均由第二步传入的参数填充,x-logo 则是额外塞进去的 ReDoc 专属字段。理论上你可以在生成后对 schema 的任何顶层或嵌套对象(paths、components.schemas、单个 operation 等)做同样的字典级修改,这正是"extending OpenAPI"最通用的姿势。
第 4 步:缓存 schema
生成的 schema 比较昂贵(要遍历全部路由、汇总 Pydantic 模型定义等),所以把结果写回 app.openapi_schema 作为缓存。这样 schema 只生成一次,之后所有请求(例如用户反复打开文档页)都会直接命中缓存:
app.openapi_schema = openapi_schema
return app.openapi_schema
注意第 2 步开头的 if app.openapi_schema: 检查:第二次调用 custom_openapi() 时缓存已存在,直接返回,避免重复生成。
第 5 步:用新函数替换默认方法
最后,把实例方法替换为自定义函数。由于 /openapi.json 端点内部调用的是 self.openapi(),替换后该端点(以及 Swagger UI、ReDoc 拉取的 schema)就会自动走你的定制逻辑:
app.openapi = custom_openapi
验证效果与测试佐证
保存并运行应用后,访问 http://127.0.0.1:8000/redoc 即可看到文档导航栏顶部显示自定义 Logo(本例为 FastAPI 官方 Logo),标题与版本也会变成 Custom title (2.5.0):
这个例子并非孤证——仓库中有配套的自动化测试 tests/test_tutorial/test_extending_openapi/test_tutorial001.py。测试先请求 /items/ 断言业务接口正常,再请求 /openapi.json 并以内联快照(inline snapshot)逐字段校验生成结果,其中 info 对象精确包含:
{
"title": "Custom title",
"summary": "This is a very custom OpenAPI schema",
"description": "Here's a longer description of the custom **OpenAPI** schema",
"version": "2.5.0",
"x-logo": {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
}
有趣的是测试还验证了文档强调的"缓存只生成一次":代码注释 # Request again to test the custom cache 之后再次请求 /openapi.json,并断言两次响应的 JSON 完全一致。同时通过快照可以发现 get_openapi() 还自动生成了 openapi: 3.1.0、operation 的 summary/operationId、默认 200 响应等常规内容——这说明定制是在"完整生成"之上做增量修改,而不是放弃 FastAPI 的默认能力。
进阶注意事项
缓存与路由变更
默认实现的 .openapi() 会记录 _openapi_routes_version,在路由树版本变化时自动重建 schema。但一旦覆写为 custom_openapi,这个失效机制是否仍生效取决于你的实现。如果应用是启动后固定路由,上述"只缓存一次"的模式没有问题;如果会在运行时动态增删路由(较少见),则需要自己在 custom_openapi() 中加入类似的版本比对或失效策略。
惰性生成与属性赋值时机
app.openapi_schema 初始为 None(见 applications.py)。自定义函数中"先判空、再生成、再赋值"的顺序保证了 schema 在首次访问时才生成(惰性),而不是模块导入时立即计算,避免拖慢启动过程。
更细粒度的替代方案
如果只是为单个路由补充 OpenAPI 字段,不必走到覆写 .openapi() 的层面:FastAPI 的路由声明支持 openapi_extra,get_openapi_path() 在生成单条 operation 时会通过 deep_dict_update(operation, route.openapi_extra) 将其合并进结果(见 utils.py)。覆写 .openapi() 更适合需要全局性修改(如注入第三方厂商扩展、统一改写所有 operationId 或 tags)的场景。
小结
FastAPI 把"生成 OpenAPI schema"做成了一个可插拔的流程:.openapi() 负责入口与缓存,get_openapi() 负责真正的生成。要扩展它,只需复用 get_openapi(title=..., version=..., routes=app.routes) 生成一份完整 schema,以普通 dict 的方式修改需要定制的部分(如 ReDoc 的 x-logo),写回 app.openapi_schema 缓存,最后用 app.openapi = custom_openapi 替换默认方法即可。参考实现见 docs_src/extending_openapi/tutorial001_py310.py,完整测试见 tests/test_tutorial/test_extending_openapi/test_tutorial001.py。
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 StartedRust0629
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证件照制作算法。Python07
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
