首页
/ FastAPI 元数据(Metadata)与交互式文档 URL 配置完整指南

FastAPI 元数据(Metadata)与交互式文档 URL 配置完整指南

2026-09-06 18:34:11作者:薛曦旖Francesca

导读: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.pyopenapi/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_infoname 就是必填项
identifier str API 所用许可证的 SPDX 许可证表达式。自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。identifierurl 互斥,二者只能提供一个。
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** 变成加粗文字,整段介绍读起来就像一篇小说明书。

使用上面这段配置启动应用后,自动文档页面会展示出完整元数据效果:

配置了 title、summary、description、version、terms_of_service、contact 与 license_info 后 Swagger UI 首页的信息展示效果

从上图可以看到:顶部标题旁依次显示版本号 0.0.1OAS 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.pyget_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}

从源码可以看出三层事实:

  1. titleversion 是 OpenAPI info 对象唯一两个永远存在的键(OpenAPI 规范要求它们必填)。
  2. 其余字段采用"有值才写入"的惰性策略——即使某个字段为 None,也不会向 Schema 输出空键。
  3. 当前仓库中 get_openapi 默认的 openapi_version"3.1.0"(见 utils.py 第 589 行),因此 summarylicense_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 许可证表达式 identifieridentifierurl 互斥,必须二选一。

参考 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 operationsAPIRoutertags 参数里的 tag 名完全一致
  • descriptionstr,该 tag 的简短说明,支持 Markdown,会显示在文档界面;
  • externalDocsdict,描述外部文档,包含:
    • descriptionstr,外部文档的简短说明;
    • url必填):str,外部文档的 URL。

创建 tag 元数据

沿用官方示例(文件 docs_src/metadata/tutorial004_py310.py),为 usersitems 两个 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 分组展示的全部附加元数据:

通过 openapi_tags 为 users 与 items 标签配置描述及 externalDocs 后,Swagger UI 的分组展示效果

可见每个 tag 都是可折叠分组:users 分组下是 GET /users/items 分组旁还出现了指向"Items external docs"的外部文档链接。

理解 tag 的排序规则

tag 元数据字典在列表中的先后顺序,同时也决定了文档界面中的展示顺序。例如上例中,尽管按字母序 users 应当排在 items 之后,但因为我们把 users 的字典放在了列表第一个位置,所以它在文档界面中排在最前面。

这一行为可以从源码机制得到印证:在 fastapi/openapi/utils.pyget_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_urldocs_url 同时非空,就会注册 Swagger UI 页面(并把 Schema 地址、swagger_ui_oauth2_redirect_urlinit_oauthswagger_ui_parameters 等一起注入 HTML,见 applications.py 第 1121-1137 行);
  • 只要 openapi_urlredoc_url 同时非空,就会注册 ReDoc 页面(applications.py 第 1149-1158 行)。

仓库中的 tests/test_local_docs.pytests/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
)

几点实战提醒:

  • summarylicense_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。

进一步阅读

登录后查看全文
热门项目推荐
相关项目推荐