你写下一个 FastAPI 应用,跑起来,浏览器打开 /docs,眼前蹦出来一页漂漂亮亮的交互式接口文档,标题、版本号、联系方式一应俱全。很多人以为这是框架顺手给的装饰,其实背后藏着一套被叫作 metadata 的机制。它不碰业务逻辑,却决定了你的 API 在别人眼里是什么样子,漂不漂亮,专不专业。
从一个最朴素的疑问说起
先别急着背定义。设想你是团队里新来的后端,要把接口交给前端同事。你总得告诉人家,这套接口叫什么名字,现在到第几版了,出了问题找谁,用的什么开源协议吧。这些关于接口本身的描述信息 ,就是 metadata 的本意,中文常译作元数据,也就是描述数据的数据。
FastAPI 把这件事做得很彻底,它在你创建应用实例的那一刻,就允许你把这些信息一股脑塞进去,然后自动写进一份叫 OpenAPI 的标准说明书里,再喂给文档界面渲染出来 。换句话说,metadata 是源头 ,OpenAPI 是载体 ,自动文档是门面,三者环环相扣。
地基,OpenAPI 到底是什么
要把 metadata 讲透,绕不开 OpenAPI。用大白话讲,OpenAPI 是一份描述 RESTful API 的标准格式说明书,早些年大家叫它 Swagger,后来改名换姓成了现在的 OpenAPI 规范 。它规定了一份接口说明里该写哪些字段,比如有哪些路径、每个接口收什么参数、返回什么结构、要不要登录鉴权。
打个比方,OpenAPI 就像餐厅贴在墙上的那张标准菜单,顾客(前端、客户端、第三方工具)照着点菜就行,不用冲进后厨问厨师。FastAPI 厉害的地方在于,你只要用 Python 的类型标注把接口写出来,它就自动帮你把这份菜单生成好,全程不用手写一句文档 。
而 metadata 呢,就是你在生成这份菜单时,写在封面上的那些信息,标题、简介、版本、联系方式、版权声明。它们最终都落到 OpenAPI 根对象的 info 字段里 。
下面这张图把整个链路串起来,从你写的代码到别人看到的页面,metadata 始终在中间穿针引线。
API 元数据,写在封面上的七项信息
创建 FastAPI() 实例时,你可以往构造函数里塞一串可选参数,每一个都会变成 OpenAPI 里的对应字段。它们全部可省,只填你想自定义的就行,不填就用默认值 。逐个数一遍。
title ,接口的总标题,默认是 FastAPI 这个词,文档页面最顶上的大标题就是它 。建议换成你项目真正的名字,不然十个项目九个都叫 FastAPI,谁认得谁。
summary ,一句极短的摘要,OpenAPI 3.1.0 和 FastAPI 0.99.0 之后才支持,适合放那种一句话定位,比如 Deadpool 最爱的应用 。
description ,一段较长的介绍,支持 Markdown 语法,这意味着你能写标题、列表、加粗、链接,文档界面会原样渲染出来 。这一项最值得花心思,把接口能干啥、分哪几块写清楚,等于给使用者写了一份开门见山的导引。
version ,你自己的应用版本号 ,比如 2.5.0,注意它说的是你项目的版本,不是 OpenAPI 规范的版本,也不是 FastAPI 的版本,别搞混 。这里用字符串存,所以写成 $version="2.5.0" 即可,别当数字处理。
terms_of_service,服务条款的网址,给了就必须是合法 URL 。
contact ,对外公开的联系方式,是个字典,里面能放 name 、url 、email 三个字段,方便别人出问题找上门 。
license_info ,许可证信息,也是字典,至少要给 name 字段,还可以配 url 指向协议全文;从 OpenAPI 3.1.0 起,也能用 identifier 写一个 SPDX 协议标识(比如 Apache-2.0 ),它和 url 二选一,互斥 。
把上面这些摆进代码,长这样,注意这里 description 用了多行字符串,里面直接写 Markdown。
python
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"}]
跑起来再打开文档,你会发现 title 、version 、contact、许可证链接全都在页面里安了家 。这种专业感,几乎零成本就挣到了。
标签元数据,给接口分门别类
光有封面信息还不够。接口一多,文档页面会挤成一锅粥。FastAPI 允许你给每个路径操作打 tags ,比如把用户相关的全标 users ,商品相关的全标 items,文档界面就会把它们折叠成一组一组,清爽得多 。
可光有分组名还不够贴心,于是有了标签元数据 ,通过一个叫 openapi_tags 的参数传入。它接收一份列表,列表里每个字典对应一个标签,能写三样东西。
name 是必填的,必须和你在路径操作里用的标签名一字不差。description 是这个标签的介绍,同样支持 Markdown。externalDocs 是个字典,指向外部文档,里面 description 写说明,url 写链接地址 。
看一段示例,users 和 items 两个标签各配了说明,items 还顺手挂了个外部文档链接。
python
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)
@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"}]
有个细节挺妙,标签在 openapi_tags 列表里的先后,直接决定了文档里它们的展示顺序 。也就是说,文档分组从上到下的排布,是你自己用列表顺序一手安排的,而不是按字母或者代码出现的先后。这一点可以用一个简单的线性关系表达,文档顺序就是列表顺序本身。
docOrder=order(openapi_tags)
而且你大可不必给每个标签都写元数据,没写的那部分照常工作,只是少了那段说明文字罢了 。
连文档本身的网址都能改
metadata 管的是内容,FastAPI 还顺带让你掌控文档放在哪个网址,这虽不算传统意义的元数据,却和整套文档机制绑在一起,值得一并记住 。
默认情况下,那套 OpenAPI 原始 JSON 挂在 /openapi.json ,你可以用 openapi_url 改它,比如挪到 /api/v1/openapi.json 。要是图省事想彻底关掉,直接设成 openapi_url=None,连带着两个文档界面也一起消失 。
两个文档界面也各有开关。Swagger UI 默认在 /docs ,用 docs_url 改地址,设 None 就关掉;ReDoc 默认在 /redoc ,用 redoc_url 改地址,设 None 关掉 。比如你想把 Swagger 挪到 /documentation 并干脆关掉 ReDoc,就这么写。
python
from fastapi import FastAPI
app = FastAPI(docs_url="/documentation", redoc_url=None)
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
这里藏着一条工程经验,生产环境里不少人会把文档界面关掉或加限制,免得把接口结构白白暴露给外人 。毕竟文档是给内部开发和合作方看的,直接挂在公网未必是好事。
把核心概念收拢成一张网
说到这儿,涉及的几个核心概念其实就那么几层,彼此的关系用一张图收一下最清楚。
简单说,FastAPI 构造函数里的 metadata 参数 负责往 OpenAPI 的 info 里填封面信息,openapi_tags 负责给接口分组加说明,而 /docs 、/redoc 、/openapi.json 这几处网址则决定这份说明最终以什么形式、摆在哪让人看见。三股绳拧在一起,才成就了那个让人一眼就信任的专业文档。
写在最后
回过头看,FastAPI 的 metadata 一点都不玄乎,它就是让你在动手写业务之前,先把自己的 API 像模像样地介绍一遍。几行参数换来的,是自动文档顶部那行像样的标题、清晰的版本、靠谱的联系方式,还有井井有条的分组。对内部协作,它降低沟通成本;对外发布,它传递专业度。代价几乎为零,收益却实打实,这种便宜不占实在说不过去。下次起新项目,记得在 FastAPI() 那行多写几个字,你的接口会因此体面很多。
参考资料
FastAPI 官方文档,元数据和文档 URL(中文版),fastapi.tiangolo.com/zh/tutorial...
learncode.live,How FastAPI Generates the OpenAPI Spec,learncode.live/fastapi-aut...
chanhle.dev,Understanding OpenAPI with FastAPI: Auto-Generated Documentation,chanhle.dev/en/blog/und...
mintlify.wiki,Metadata and Docs URLs,mintlify.wiki/fastapi/fas...