FastAPI 元数据(Metadata)与交互式文档 URL 配置完整指南
导读:FastAPI 允许你通过
FastAPI()构造参数为应用注入一套"自我描述"信息——包括标题、摘要、说明、版本、服务条款、联系人与许可证等元数据,它们会写入 OpenAPI Schema(默认/openapi.json)并渲染到 Swagger UI 与 ReDoc 自动文档界面。同时你还可以精细控制 Schema 与两套文档页面的挂载 URL,甚至彻底关闭它们。读完本文,你将能:把文档中的参数表逐一映射到真实代码;用 Markdown 写出图文并茂的 API 简介;借助openapi_tags给接口分组配上说明与外部文档;并把文档地址定制为/api/v1/openapi.json、/documentation这样的生产级路径。
本文以仓库中的 metadata 教程文档 为主线,示例代码均可在 docs_src/metadata 目录找到,同时结合 applications.py 与 openapi/utils.py 的实现来讲解底层原理。
一、API 级元数据:填充 OpenAPI 顶层的 info 信息
FastAPI 支持在创建应用时设置一组元数据字段,它们最终会进入 OpenAPI 规范中的 info 对象,并被自动文档 UI 展示。官方文档给出的字段总表如下:
| 参数 | 类型 | 说明 |
|---|---|---|
title |
str |
API 的标题。 |
summary |
str |
API 的简短摘要。自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。 |
description |
str |
API 的简短描述,支持 Markdown 语法。 |
version |
str |
API 的版本号。注意它指的是你自己的应用版本,而不是 OpenAPI 的版本,例如 2.5.0。 |
terms_of_service |
str |
API 服务条款的 URL。一旦提供,必须是合法 URL。 |
contact |
dict |
对外 API 的联系人信息,可包含若干子字段: |
license_info |
dict |
对外 API 的许可证信息,可包含若干子字段: |
contact 支持的子字段:
| 参数 | 类型 | 说明 |
|---|---|---|
name |
str |
联系人(个人或组织)的标识名称。 |
url |
str |
指向联系信息的 URL,必须是 URL 格式。 |
email |
str |
联系人(个人或组织)的邮箱,必须是邮箱格式。 |
license_info 支持的子字段:
| 参数 | 类型 | 说明 |
|---|---|---|
name |
str |
许可证名称。只要设置了 license_info,name 就是必填项。 |
identifier |
str |
API 所用许可证的 SPDX 许可证表达式。自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。identifier 与 url 互斥,二者只能提供一个。 |
url |
str |
指向许可证文本的 URL,必须是 URL 格式。 |
完整配置示例(ChimichangApp)
以下取自 docs_src/metadata/tutorial001_py310.py,它一次性演示了上述全部字段的用法:
from fastapi import FastAPI
description = """
ChimichangApp API helps you do awesome stuff. 🚀
## Items
You can **read items**.
## Users
You will be able to:
* **Create users** (_not implemented_).
* **Read users** (_not implemented_).
"""
app = FastAPI(
title="ChimichangApp",
description=description,
summary="Deadpool's favorite app. Nuff said.",
version="0.0.1",
terms_of_service="http://example.com/terms/",
contact={
"name": "Deadpoolio the Amazing",
"url": "http://x-force.example.com/contact/",
"email": "dp@x-force.example.com",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)
@app.get("/items/")
async def read_items():
return [{"name": "Katana"}]
代码中的 description 本身就是一个长字符串,内部写了 ## Items、**read items** 等 Markdown 标记。这是因为 description 字段中的 Markdown 会被渲染在最终文档中,这一点官方文档以 tip 提示专门强调:"You can write Markdown in the description field and it will be rendered in the output."。于是在文档首页上,"Items / Users" 会被渲染成两个可折叠的分组标题,**read items** 变成加粗文字,整段介绍读起来就像一篇小说明书。
使用上面这段配置启动应用后,自动文档页面会展示出完整元数据效果:
从上图可以看到:顶部标题旁依次显示版本号 0.0.1 与 OAS 3.1 徽标;中部是 Markdown 渲染后的分组说明;页面底部则陈列着 Terms of service 链接、联系人与邮箱链接以及 Apache 2.0 许可证信息。
源码视角:这些字段如何流进 /openapi.json
理解配置归属最直接的办法是看 FastAPI 应用对象是如何生成 OpenAPI Schema 的。在 fastapi/applications.py 中,openapi() 方法把构造参数透传给 get_openapi(见 applications.py 第 1086-1101 行):
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,
...
tags=self.openapi_tags,
...
)
而真正的"组装"发生在 fastapi/openapi/utils.py 的 get_openapi 中(见 utils.py 第 602-613 行):
info: dict[str, Any] = {"title": title, "version": version}
if summary:
info["summary"] = summary
if description:
info["description"] = description
if terms_of_service:
info["termsOfService"] = terms_of_service
if contact:
info["contact"] = contact
if license_info:
info["license"] = license_info
output: dict[str, Any] = {"openapi": openapi_version, "info": info}
从源码可以看出三层事实:
title与version是 OpenAPIinfo对象唯一两个永远存在的键(OpenAPI 规范要求它们必填)。- 其余字段采用"有值才写入"的惰性策略——即使某个字段为
None,也不会向 Schema 输出空键。 - 当前仓库中
get_openapi默认的openapi_version是"3.1.0"(见 utils.py 第 589 行),因此summary、license_info.identifier这些 3.1 时代的新特性均可使用。
另外,在 applications.py 第 926-927 行 还有一处前置校验:只要 openapi_url 非空,就会断言 title 必须提供(assert self.title)。这是 OpenAPI 规范要求的"必须有标题",如果你设置 openapi_url 却忘了 title,FastAPI 会在生成 Schema 时直接抛出断言错误。
二、用 SPDX identifier 声明许可证
在 license_info 中,除了用 url 指向许可证全文链接,自 OpenAPI 3.1.0 / FastAPI 0.99.0 起还可以改用 SPDX 许可证表达式 identifier。identifier 与 url 互斥,必须二选一。
参考 docs_src/metadata/tutorial001_1_py310.py(其余部分与上一示例相同,这里只看 license_info 的差异):
license_info={
"name": "Apache 2.0",
"identifier": "Apache-2.0",
},
"Apache-2.0" 就是 SPDX 官方规定的短标识符。相比 url 方案,identifier 更简洁且机器可读,方便开源合规工具自动解析;许可证列表由 SPDX 组织维护,项目方只需保证写法与 SPDX 短标识符一致即可。
三、Tag 元数据:让按模块分组的文档更专业
当路径操作较多时,你会用 tags=["users"] 之类的参数把接口归组(可参见 Path Operation Configuration 中关于 tags 的章节)。FastAPI 额外提供了 openapi_tags 参数,让你为每个 tag 补充说明与外部文档链接,从而让自动文档页更有结构感。
openapi_tags 接收一个列表,列表中的每一项是一个字典,分别对应一个 tag。每个字典可以包含:
name(必填):str,必须与你写在 path operations 与APIRouter的tags参数里的 tag 名完全一致;description:str,该 tag 的简短说明,支持 Markdown,会显示在文档界面;externalDocs:dict,描述外部文档,包含:description:str,外部文档的简短说明;url(必填):str,外部文档的 URL。
创建 tag 元数据
沿用官方示例(文件 docs_src/metadata/tutorial004_py310.py),为 users 与 items 两个 tag 准备元数据,并传给 openapi_tags:
from fastapi import FastAPI
tags_metadata = [
{
"name": "users",
"description": "Operations with users. The **login** logic is also here.",
},
{
"name": "items",
"description": "Manage items. So _fancy_ they have their own docs.",
"externalDocs": {
"description": "Items external docs",
"url": "https://fastapi.tiangolo.com/",
},
},
]
app = FastAPI(openapi_tags=tags_metadata)
注意两个细节:
- 描述中同样可以使用 Markdown,例如
**login**会以加粗显示,_fancy_会以_斜体_显示; - 官方以 tip 提醒:你并不需要为所有用到的 tag 都补充元数据,只写想美化/说明的部分即可,其余 tag 仍会正常出现在文档中。
在路径操作中挂上 tag
元数据定义好后,还要用 tags 参数把路径操作"挂"到对应 tag 上:
@app.get("/users/", tags=["users"])
async def get_users():
return [{"name": "Harry"}, {"name": "Ron"}]
@app.get("/items/", tags=["items"])
async def get_items():
return [{"name": "wand"}, {"name": "flying broom"}]
启动应用后打开文档页,就能看到按 tag 分组展示的全部附加元数据:
可见每个 tag 都是可折叠分组:users 分组下是 GET /users/,items 分组旁还出现了指向"Items external docs"的外部文档链接。
理解 tag 的排序规则
tag 元数据字典在列表中的先后顺序,同时也决定了文档界面中的展示顺序。例如上例中,尽管按字母序 users 应当排在 items 之后,但因为我们把 users 的字典放在了列表第一个位置,所以它在文档界面中排在最前面。
这一行为可以从源码机制得到印证:在 fastapi/openapi/utils.py 的 get_openapi 末尾(第 675-676 行),传入的 tags 列表被原样、保序地写入顶层 output["tags"],中间没有任何重排逻辑;而每个路径操作的 tags 则通过 get_openapi_operation_metadata 原样写入该 operation(第 291-292 行)。因此"想让它排前就把它放前面",规则与直觉完全一致。
四、控制 OpenAPI Schema 的 URL
默认情况下,OpenAPI Schema 会被挂在 /openapi.json 下(这也是 Swagger UI 与 ReDoc 读取 Schema 的来源)。你可以用 openapi_url 参数改变它的地址,例如挂到 /api/v1/openapi.json:
from fastapi import FastAPI
app = FastAPI(openapi_url="/api/v1/openapi.json")
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
如果想彻底关闭 OpenAPI Schema,把 openapi_url 设为 None 即可。要注意:由于 Swagger UI 和 ReDoc 都依赖这个 Schema,openapi_url=None 会连带禁用两套交互式文档 UI。
源码在 applications.py 的 setup() 方法 中清楚体现了这一耦合关系:
if self.openapi_url:
async def openapi(req: Request) -> JSONResponse:
root_path = req.scope.get("root_path", "").rstrip("/")
schema = self.openapi()
...
self.add_route(self.openapi_url, openapi, include_in_schema=False)
if self.openapi_url and self.docs_url:
async def swagger_ui_html(req: Request) -> HTMLResponse:
...
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
...
if self.openapi_url and self.redoc_url:
...
self.add_route(self.redoc_url, redoc_html, include_in_schema=False)
也就是说:三个路由的注册条件是逐级依赖的——openapi_url 是所有功能的开关,docs_url/redoc_url 又各自独立可控。此外,applications.py 第 1108-1118 行 还显示,/openapi.json 端点返回的是一个 JSONResponse,其内部 root_path 处理使应用被部署在带前缀的反向代理之后时,Schema 仍能正确生成(这与背后的 root_path 机制相关)。
五、自定义交互式文档的 URL
FastAPI 内置两套交互式文档界面,均可自由换址或关闭:
- Swagger UI:默认挂载于
/docs。- 通过参数
docs_url修改地址; - 设为
docs_url=None可禁用。
- 通过参数
- ReDoc:默认挂载于
/redoc。- 通过参数
redoc_url修改地址; - 设为
redoc_url=None可禁用。
- 通过参数
例如下面的配置把 Swagger UI 改到 /documentation,同时完全关掉 ReDoc(docs_src/metadata/tutorial003_py310.py):
from fastapi import FastAPI
app = FastAPI(docs_url="/documentation", redoc_url=None)
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
从上一节的 setup() 源码可以看到这套组合逻辑:
- 只要
openapi_url与docs_url同时非空,就会注册 Swagger UI 页面(并把 Schema 地址、swagger_ui_oauth2_redirect_url、init_oauth、swagger_ui_parameters等一起注入 HTML,见 applications.py 第 1121-1137 行); - 只要
openapi_url与redoc_url同时非空,就会注册 ReDoc 页面(applications.py 第 1149-1158 行)。
仓库中的 tests/test_local_docs.py 与 tests/test_swagger_ui_escape.py 正是围绕这类文档/接口 URL 行为编写的回归测试,读者可以结合它们进一步验证自定义 URL 与开关配置的实际效果。
六、组合实战:一份面向生产环境的配置清单
把以上所有参数串在一起,可以得到一个既能自定义 Schema 路径、又能统一文档入口、还能关闭不必要界面并补充 tag 说明的完整示例:
from fastapi import FastAPI
tags_metadata = [
{
"name": "users",
"description": "Operations with users. The **login** logic is also here.",
},
{
"name": "items",
"description": "Manage items. So _fancy_ they have their own docs.",
"externalDocs": {
"description": "Items external docs",
"url": "https://example.com/docs/items/",
},
},
]
app = FastAPI(
title="My Production API",
summary="Core backend service",
description="""Serves the main business capabilities.
## Modules
- **users**: account & authentication related endpoints.
- **items**: inventory management.
""",
version="2.5.0",
terms_of_service="https://example.com/terms/",
contact={
"name": "API Support Team",
"url": "https://example.com/support/",
"email": "api@example.com",
},
license_info={
"name": "MIT",
"identifier": "MIT",
},
openapi_tags=tags_metadata,
openapi_url="/api/v1/openapi.json",
docs_url="/documentation",
redoc_url=None, # 生产环境可仅保留一套文档 UI
)
几点实战提醒:
summary、license_info.identifier依赖 OpenAPI 3.1,当前仓库默认openapi_version="3.1.0",可直接使用;若你的下游工具只支持 OpenAPI 3.0,这些字段不会生效。version是你自己应用的发版号,与 OpenAPI 版本无关;不显式配置时,FastAPI 会使用默认值(官方示例界面中可见默认0.1.0)。description与 tag 描述都支持 Markdown,务必善用加粗、列表与二级标题,让/openapi.json之外的"人读文档"体验与机器读 Schema 同等重要。- 关闭某界面采用
xxx_url=None,但要注意openapi_url=None属于"一刀切",会同时禁用文档 UI。
进一步阅读
- 本页英文原文:docs/en/docs/tutorial/metadata.md
- 全部配套示例代码:docs_src/metadata
- 在路径操作上使用
tags进行分组:Path Operation Configuration FastAPI构造参数与setup()路由注册源码:fastapi/applications.py- OpenAPI Schema 组装逻辑(
info对象、顶层tags保序写入):fastapi/openapi/utils.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 StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

