为 FastAPI 的 SwaggerUI 定制 CSS 样式

FastAPI 比起 Flask 而言一个十分便利的功能是它内置对 Swagger UI 文档的支持,然而默认生成的 Swagger UI 也总不尽如人意,于是就有了如何通过引入自己的样式(或样式文件)对默认 Swagger UI 进行定制化的需求。在 ChatGPT 之前,Google 和阅读源代码是齐头并进的选择,自己有了 ChatGPT 之类的 AI, 人们一下就把身段放低了许多,再也不像使用 Google 那样的心态去使用 AI 了。所以呢,第一次支持付了 $8 问问当前号称最厉害的 Grok 3(也算是对 DOGE 的支持吧), 得到答案如下

1app.mount("/static", StaticFiles(directory="static"), name="static")<br/><br/>
2app = FastAPI(
3    swagger_ui_parameters={
4        "css_url": "/static/custom_swagger.css"
5    }
6)

在网站的 /static 目录下也创建了 custom_swagger.css 文件,然而根本就没有效果,Inspect 浏览器后发现 FastAPI 的 /docs 根本就有加载 /static/custom_swagger.css 文件。

再追问一下去,又给出了下面的答案
 1@app.get("/docs", include_in_schema=False)
 2async def custom_swagger_ui_html():
 3    return get_swagger_ui_html(
 4        .... # 此处省略
 5        custom_css="""
 6            &lt;style&gt;
 7                body { background-color: #f0f0f0; }
 8            &lt;/style&gt;
 9        """
10    )

这样跟随着 AI 坠入了它的自我幻觉当中。上面两个答案都是 AI 在无中生有的,为迎合人类而进行的创造。

对于第一个答案,SwaggerUI 根本就不支持 "css_url" 这个参数,完整的支持参数可参考:Swagger UI Configuration Docs.

而第二个答案,也类似,AI 为 FastAPI 的 get_swagger_ui_html() 函数莫名的新加了一个 "custom_css" 参数,FastAPI 的 get_swagger_ui_html() 函数原型在这里 fastapi/fastapi/openapi/docs.py

经过此番 AI 的实践,更是浪费了不少的时间,如果运气好些找到了有效的答案,对于掌握相应的知识基本是鲜有帮助。

要说上面唯一线索只有,定制 /docs 可以显式的调用 get_swagger_ui_html() 函数,这就是我们能发挥的地方。

首先再次回到 Grok 3 给出的答案一,加在 FastAPI 的 swagger_ui_parameters 参数中的属性会加到 /docs 页面的 SwaggerUIBundler() 调用
 1    <script>
 2    const ui = SwaggerUIBundle({
 3        url: '/openapi.json',
 4    "dom_id": "#swagger-ui",
 5"layout": "BaseLayout",
 6"deepLinking": true,
 7"showExtensions": true,
 8"showCommonExtensions": true,
 9"css_url": "/static/custom_swagger.css",
10oauth2RedirectUrl: window.location.origin + '/docs/oauth2-redirect',
11    presets: [
12        SwaggerUIBundle.presets.apis,
13        SwaggerUIBundle.SwaggerUIStandalonePreset
14        ],
15    })
16    </script>

由于 SwaggerUIBundle() 并不支持 "css_url" 属性,在 Swagger UI Configuration Docs 中也没有找到定制 CSS 文件的解决方案。

下面展开如何利用 get_swagger_ui_html() 函数来定制 CSS,从一个完整的 FastAPI 例子开讲。本例不用 fastapi 命令 + main.py 的方式启动 Web 服务,而是显式的用 uvicorn 在代码中启动服务。

本例所需安装的 Python 依赖是
pip install "fastapi[standard]"
完整的 main.py 代码是
 1from fastapi import FastAPI, Security, Path
 2from fastapi.security.api_key import APIKeyHeader
 3from pydantic import BaseModel
 4import uvicorn
 5
 6app = FastAPI()
 7
 8api_key_header = APIKeyHeader(name="x-api-key")
 9
10
11class User(BaseModel):
12    userId: int
13    userName: str
14
15
16@app.get("/users/{userId}", response_model=User)
17def get_user(user_id: str = Path(..., alias="userId"), api_key=Security(api_key_header)):
18    return {"userId": 0, "userName": "Scott"}
19
20
21if __name__ == '__main__':
22    uvicorn.run(app, host="0.0.0.0", port=8080)

启动之后,打开 http://0.0.0.0:8080/docs 看到 SwaggerUI 界面是

这个界面有点拉胯,想要通过 CSS 定制实现如面更为紧凑的 UI, 同时去除 Schemas


图 2


如果能简单的通过某个属性或参数引用自定义的 css, 进而调整 UI 的 Authorize 按钮的 margin-top: -120px, 再把  'openapi.json' 链接隐去就好,实际上还有些曲折。在这里隐去 "Schemas"  部分可在声明 FastAPI 时指定属性
1app = FastAPI(
2    swagger_ui_parameters={
3        "defaultModelsExpandDepth": -1   # 这样便不在 SwaggerUI 中显示 Schemas
4    }
5)

通过 Google 或 AI, 我们定位到访问 FastAPI 的 /docs 实际就是调用了 get_swagger_ui_html() 函数,可以覆盖 /docs 接口。于是等效的 /docs 就是
 1from fastapi.openapi.docs import get_swagger_ui_html
 2from fastapi.responses import HTMLResponse
 3
 4app = FastAPI(docs_url=None)
 5
 6@app.get("/docs", include_in_schema=False)
 7def custom_swagger_ui_html() -> HTMLResponse:
 8   return get_swagger_ui_html(
 9        openapi_url="/openapi.json",
10        title="FastAPI",
11        swagger_ui_parameters={
12            "defaultModelsExpandDepth": -1,
13        }
14    )

注意,我们在覆盖 /docs 时需要在声明 FastAPI 时用 docs_url=None 把默认的 /docs 禁用掉。访问 http://0.0.0.0:8080/docs 就是

后面的工作就要了解 get_swagger_ui_html() 方法原型了。
 1def get_swagger_ui_html(
 2    *,
 3    openapi_url,
 4    title: ,
 5    swagger_js_url = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
 6    swagger_css_url = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
 7    swagger_favicon_url = "https://fastapi.tiangolo.com/img/favicon.png",
 8    oauth2_redirect_url = None,
 9    init_oauth = None,
10    swagger_ui_parameters = None,
11) -> HTMLResponse:

这里有两条思路

  1. swagger_css_url 指定自己的 custom_swagger.css, 然后把默认样式 https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css 内容复制 custom_swagger.css 文件中,然后添加自己的内容。这种方式必须时刻留意默认的 swagger-ui.css 内容有列新。
  2. 该函数返回了一个 HTMLResponse, 我们可以去其中的 HTML 进行后处理,然后再返回一个重新包装的 HTMLResponse

本文采用第二种方式。思路理通了,那就开始实际,修改 main.py 后完整的代码如下
 1from fastapi import FastAPI, Security, Path
 2from fastapi.security.api_key import APIKeyHeader
 3from pydantic import BaseModel
 4import uvicorn
 5import re
 6
 7from fastapi.openapi.docs import get_swagger_ui_html
 8from fastapi.responses import HTMLResponse
 9from fastapi.staticfiles import StaticFiles
10
11app = FastAPI(docs_url=None)
12app.mount("/static", StaticFiles(directory="static"), name="static")
13
14
15@app.get("/docs", include_in_schema=False)
16def custom_swagger_ui_html() -> HTMLResponse:
17    res: HTMLResponse = get_swagger_ui_html(
18        openapi_url="/openapi.json",
19        title="FastAPI",
20        swagger_ui_parameters={
21            "defaultModelsExpandDepth": -1
22        }
23    )
24
25    new_body = re.sub(
26        r'(<link type="text/css" rel="stylesheet"[^>]+>)',
27        r'\1\n    <link type="text/css" rel="stylesheet" href="./static/custom_swagger.css">',
28        res.body.decode())
29    return HTMLResponse(new_body)
30
31
32api_key_header = APIKeyHeader(name="x-api-key")
33
34
35class User(BaseModel):
36    userId: int
37    userName: str
38
39
40@app.get("/users/{userId}", response_model=User)
41def get_user(user_id: str = Path(..., alias="userId"), api_key=Security(api_key_header)):
42    return {"userId": 0, "userName": "Scott"}
43
44
45if __name__ == '__main__':
46    uvicorn.run(app, host="0.0.0.0", port=8080)

我们需要支持静态文件,所以需要在工程目录中创建文件 static/custom_swagger.css, 其中加入需要的样式定义,本实例的内容是
1div.scheme-container {
2    margin-top: -120px !important;
3}
4
5div.info span.url {
6    display: none;
7}

如此我们最终就是实现 图 2 中的简洁效果。

最后我们不妨查看一下修改后 http://0.0.0.0:8080/docs 的网页源文件,没多少行,所以列在下面
 1    <!DOCTYPE html>
 2    <html>
 3    <head>
 4    <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css">
 5    <link type="text/css" rel="stylesheet" href="./static/custom_swagger.css">
 6    <link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
 7    <title>FastAPI</title>
 8    </head>
 9    <body>
10    <div id="swagger-ui">
11    </div>
12    <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
13    <!-- `SwaggerUIBundle` is now available on the page -->
14    <script>
15    const ui = SwaggerUIBundle({
16        url: '/openapi.json',
17    "dom_id": "#swagger-ui",
18"layout": "BaseLayout",
19"deepLinking": true,
20"showExtensions": true,
21"showCommonExtensions": true,
22"defaultModelsExpandDepth": -1,<br/><br/>
23    presets: [
24        SwaggerUIBundle.presets.apis,
25        SwaggerUIBundle.SwaggerUIStandalonePreset
26        ],
27    })
28    </script>
29    </body>
30    </html>

从中可以看到我们通过正则表达式在原有的 swagger-ui.css link 后加上了自己的 /static/custom_sagger.css, 仅此而已。同时注意到 JS 函数 SwaggerUIBundle 的参数值,有助于我们理解如 FastAPI 的函数 get_swagger_ui_html() 是怎么影响到 JS 的 SwaggerUIBundler 函数的调用。

后记:关于 AI

确实很久没更新博客了,一则原来的 VPS 三天两头宕机,好像数据库哪出了问题,现在已迁到 AWS,内存增加到了 2G 稍好些了,但仍未治本,还得努力找原因。再则如今 AI 大行其道,似乎要完全代替人的思考了,很多不再通过搜索引擎访问到博客站点了。以前的 “内事不决问百度,外事不决问谷歌,房事不决问天涯”,恐怕要改成 “万事不决问 AI” 了,不管是 ChatGPT, Grok, Claude, Gemini 或是其他的。如果产生了对 AI 的严重依赖,同样了问题问过 AI 十来遍,会发现它答案基本就没经过大脑,只有一次次的复制粘贴过程。

从前有人一个同样的问题询问我三五遍,心里头就有过起伏 -- 即便是记忆差吧,那用笔记或记事本记录录下来也不至于此吧。现今自己用 AI 不断的重复问相同的问题就是同样的感觉。Google 即使不像书籍那样可以系统性的学习新技能,那怎么着还会让我们有提炼关键字,寻找线索,不断探索的过程。而用 AI 连基本的思考都省了,把有用没用的信息全丢给 AI 处理,经常会得到幻觉式的信息。AI 有时候就像是鸦片,产生了依赖便时时离不开它,Google 搜索后链接不设置为 target="_blank" 的用意(找到答案,快速离开)有所不同。难怪 AI 要上升为一个国家战略,因为某些时候极有用。

程序员当初被称作 Google 或 StackOver 的搬运工,那 AI 时代如何称呼呢?StackOver 怎么着也是真实的人的求索,验证的过程。最初 AI 的内容也是来自 Wikipedia, StackOver, Reddit 那样的由人产生的内容,现在可好,机器也会产生大量的内容,吐到各个社区,而后又被机器吃回去。试想一下,如果那些社区全被机器占领了,AI 一本正经说假话,进入幻觉模式的机会就更大了。 永久链接 https://yanbin.blog/customize-fastapi-swaggerui-css/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。