云计算百科
云计算领域专业知识百科平台

小白python入门 - 50. OpenAPI 与接口契约

小白python入门 – 50. OpenAPI与接口契约

1. 本课定位:是什么、为何重要

前几课书签 API 已能跑路由、模型、鉴权与后台任务。一到联调,前端仍常问:分页是 items 还是 data?404 长什么样?改字段要不要通知?只丢仓库链接会反复试错。

本节把「能生成文档」升级为「能协作的契约」:OpenAPI、/docs、tags、Field 描述、分页与错误形状、/api/v1 版本、导出 openapi.json。学完应能把 Bookmark API 写成人与工具都能消费的约定。

FastAPI 自动生成 OpenAPI(/docs、/redoc、/openapi.json)。「能生成」≠「能联调」。

概念一句话
OpenAPI HTTP API 的机器可读规范
接口契约 双方同意的路径、字段、错误、鉴权
Swagger UI 浏览器交互文档 /docs
版本化 破坏性变更如何并存与迁移
契约导出 openapi.json 交给工具/SDK

为何重要: API 是团队边界产品;契约不清,联调成本指数上升。

已学本课
注解「顺便」出文档 主动写到可协作
会写路由 约定分页/错误/版本
/docs 能点 openapi.json 能导出
字段能过校验 字段有说明与示例
做法像什么对应
口头说有列表 菜单无配料 无契约
有路径无描述 菜单只有菜名 半成品
统一分页错误 统一点餐单 可复用
/api/v1 大改出第 2 版 可迁移
导出 json 标准菜单给平台 工具导入

2. 本质:文档即契约的一部分

很多人以为 OpenAPI 是写完再补的说明。在 FastAPI 里,路径、类型、response_model、状态码、tags、Field 都会进规范——改代码常即改契约。本质是把 HTTP 边界说清,让人、浏览器、工具共用同一真相。

上节说了协作痛。本节分开「实现细节」与「对外承诺」:内部名可改,对外 JSON 与错误体应稳定。先有契约感,再写 tags 与分页模型。

本质: 文档是契约载体;生成来自代码,描述与约定靠主动补齐。

注解/模型/tags → OpenAPI → /docs 给人 | openapi.json 给工具 | 测试锁形状

层级内容消费方
路径方法 GET /api/v1/bookmarks 前端、测试
请求模型 Query/Body 约束 校验+文档
响应模型 PageOut/BookmarkOut 类型与示例
错误形状 {"detail":"…"} 统一错误处理
元信息 title/version/description 对接说明
修改前修改后
返回一个 list 固定 items/total/page/size
找不到就抛 404 + detail 约定
以后再写文档 改接口先想破坏性
无 description Field 带说明与 examples

3. 约束与常见坑

契约最怕两张皮,或每接口一套分页字段;破坏性变更不升版本会让前端无声全红;examples 写真实密钥则文档一导出即泄密。

约束与坑表钉红线后再写 Field。综合实践按统一约定实现 PageOut 与 404。

约束: ①破坏性变更升版本或兼容期 ②分页/时间/错误全项目统一 ③鉴权在文档可见 ④示例用假数据 ⑤以运行中 openapi.json 为准 ⑥业务路径用 /api/v1。

坑现象正确直觉
文档实现两张皮 按文档联调失败 以生成规范为准并补描述
分页字段各异 组件难复用 统一四字段
无版本前缀 破坏无处可逃 /api/v1
示例含密钥 泄密 假数据
只写 200 错误分支瞎猜 responses+detail
暴露内部字段 重构即破坏 稳定 response_model
兼容破坏性
新增可选字段/接口 删改字段名或类型
放宽校验 收紧致旧客户端失败
补充错误说明 改鉴权无兼容

4. 约定表:分页、错误、鉴权、版本

契约宜少而硬。书签 API 入门:分页四字段、错误 detail、ISO8601、Bearer、URL 版本。可直接贴进对接说明。

主题入门约定
分页 items+total+page+size
时间 ISO8601,优先 UTC
错误 {"detail":"…"}(422 为列表)
鉴权 Authorization: Bearer <token>
版本 /api/v1 前缀
过滤 Query q 等可选

{"items":[{"id":1,"title":"FastAPI 文档","url":"https://fastapi.tiangolo.com"}],"total":1,"page":1,"size":10}

场景码body
未登录 401 {"detail":"Not authenticated"}
校验失败 422 {"detail":[…]}
不存在 404 {"detail":"bookmark not found"}
冲突 409 {"detail":"…"}
增强手段作用
tags / openapi_tags 分组
summary / docstring 人话标题
Field(description/examples) 字段说明
responses={404:…} 错误说明
version/description 元信息
导出 openapi.json 工具导入

5. tags 与应用元信息

接口一多 /docs 变成长列表。tags 分组;openapi_tags 写组说明;title/version/description 做对接抬头。不改业务,却显著提升可读性。

项作用
title 如 Bookmark API
version 如 1.0.0
description Markdown 对接说明
openapi_tags 组 name+description
路由 tags 接口归组
summary 列表短标题

小步:

from fastapi import FastAPI
app = FastAPI(
title="Bookmark API", version="1.0.0",
description="书签服务入门契约",
openapi_tags=[
{"name": "meta", "description": "健康检查"},
{"name": "bookmarks", "description": "书签资源"},
],
)
@app.get("/health", tags=["meta"], summary="健康检查")
def health():
return {"status": "ok"}

预期: /docs 有 meta 组与「健康检查」标题。

修改前修改后
全堆默认组 meta/bookmarks 分组
无版本 version 可见
仅路径串 summary 人话

6. Field 描述、示例与 response_model

类型只保证 int/str,不保证「页码从 1」。Field/Query 的 description、examples 进 OpenAPI;response_model 固定成功形状。

写法进文档
title: str 类型
Field(description=…) 描述
Field(examples=[…]) 假数据示例
Query(1, ge=1, description=…) 默认+约束+描述
response_model=PageOut 成功 schema

小步:

from pydantic import BaseModel, Field
from fastapi import Query

class BookmarkOut(BaseModel):
id: int = Field(description="书签 ID", examples=[1])
title: str = Field(description="标题", examples=["FastAPI 文档"])
url: str = Field(description="链接", examples=["https://fastapi.tiangolo.com"])

class PageOut(BaseModel):
items: list[BookmarkOut]
total: int = Field(description="总条数")
page: int = Field(description="页码从 1")
size: int = Field(description="每页条数")

def params(
page: int = Query(1, ge=1, description="页码从 1"),
size: int = Query(10, ge=1, le=100, description="每页条数"),
q: str | None = Query(None, description="标题关键字"),
):
return page, size, q

预期: /docs 参数有中文说明;Schemas 有 PageOut 四字段。

修改前修改后
猜 data/items 固定 items
页码口头约定 description 写死
多返回内部字段 response_model 裁剪

7. 错误形状与 responses

成功体写清后,错误仍是黑洞。422 默认来自校验;业务用 HTTPException 统一 detail;responses={404:…} 让文档显示失败说明。

来源码detail
缺 Token 401 字符串
非法 Body/Query 422 列表
HTTPException 自定 通常字符串
未捕获 500 生产隐藏细节

小步:

from fastapi import FastAPI, HTTPException, Path
app = FastAPI()

@app.get("/api/v1/bookmarks/{bookmark_id}", responses={404: {"description": "不存在"}})
def get_bookmark(bookmark_id: int = Path(..., description="书签 ID")):
if bookmark_id != 1:
raise HTTPException(status_code=404, detail="bookmark not found")
return {"id": 1, "title": "demo", "url": "https://example.com"}

预期: 999→404+detail;/docs 见 404 说明。

修改前修改后
{"error":…} 各异 统一 detail
文档仅 200 声明 404

8. 版本化 /api/v1 与导出 openapi.json

字段破坏性变更时,无版本路径会让旧客户端无声坏掉。入门推荐 URL 前缀:/api/v1 稳定,破坏性开 /api/v2。机器侧导出 /openapi.json 给 Apifox/Postman/SDK。

策略优劣建议
URL /api/v1 直观 略长 推荐
Header 版本 路径干净 难调试 了解
永久兼容 无迁移 包袱重 难坚持
变更建议
新增可选字段 v1 内加并文档注明
删除必填/改类型 新版本或兼容期
改鉴权 公告+双轨

/api/v1/bookmarks /api/v2/bookmarks /health /openapi.json

交付物给谁
/docs /redoc
/openapi.json 工具
description/README 前端对接

curl -s http://127.0.0.1:8000/openapi.json -o openapi.json

检查:含 paths、PageOut、tags;examples 无真实密钥。


9. 综合实践:可协作的 Bookmark API 契约

合成最小可运行服务:健康检查、分页列表、详情 404、统一 PageOut、路径在 /api/v1、description 写对接约定。Windows 若 cat <<'EOF' 不可用,请用 Cygwin / Git Bash / WSL,或手动建 main.py。

mkdir -p ~/python-lab/src/day50 && cd ~/python-lab/src/day50
pip install fastapi "uvicorn[standard]"

cat > main.py << 'EOF'
from fastapi import FastAPI, HTTPException, Path, Query
from pydantic import BaseModel, Field

description = """
## Bookmark API 对接说明(Day50)
1. 本演示无真实登录。
2. 分页:`items` / `total` / `page` / `size`。
3. 错误:`{"detail": "…"}`(422 为列表)。
4. 业务路径:`/api/v1`。
5. 导出:`/openapi.json`。
"""

app = FastAPI(
title="Bookmark API", description=description, version="1.0.0",
openapi_tags=[
{"name": "meta", "description": "健康检查与元信息"},
{"name": "bookmarks", "description": "书签资源"},
],
)

class BookmarkOut(BaseModel):
id: int = Field(description="书签 ID", examples=[1])
title: str = Field(description="标题", examples=["FastAPI 文档"])
url: str = Field(description="链接", examples=["https://fastapi.tiangolo.com"])
tags: list[str] = Field(default_factory=list, description="标签", examples=[["python"]])

class PageOut(BaseModel):
items: list[BookmarkOut]
total: int
page: int
size: int

_DB = [
BookmarkOut(id=1, title="FastAPI 文档", url="https://fastapi.tiangolo.com", tags=["python", "web"]),
BookmarkOut(id=2, title="Pydantic", url="https://docs.pydantic.dev", tags=["python"]),
BookmarkOut(id=3, title="OpenAPI 规范", url="https://swagger.io/specification/", tags=["api"]),
]

@app.get("/health", tags=["meta"], summary="健康检查")
def health():
return {"status": "ok", "version": "1.0.0"}

@app.get("/api/v1/bookmarks", tags=["bookmarks"], summary="分页列出书签", response_model=PageOut)
def list_bookmarks(
page: int = Query(1, ge=1, description="页码从 1"),
size: int = Query(10, ge=1, le=100, description="每页条数"),
q: str | None = Query(None, description="标题关键字"),
):
rows = _DB
if q:
rows = [x for x in rows if q.lower() in x.title.lower()]
start = (page – 1) * size
return PageOut(items=rows[start:start + size], total=len(rows), page=page, size=size)

@app.get(
"/api/v1/bookmarks/{bookmark_id}", tags=["bookmarks"], summary="书签详情",
response_model=BookmarkOut, responses={404: {"description": "不存在"}},
)
def get_bookmark(bookmark_id: int = Path(…, ge=1, description="书签 ID")):
for item in _DB:
if item.id == bookmark_id:
return item
raise HTTPException(status_code=404, detail="bookmark not found")
EOF

uvicorn main:app –reload –port 8000

自测:

curl -s http://127.0.0.1:8000/openapi.json -o openapi.json
curl -s "http://127.0.0.1:8000/api/v1/bookmarks?page=1&size=2"
curl -s "http://127.0.0.1:8000/api/v1/bookmarks?q=OpenAPI"
curl -s http://127.0.0.1:8000/api/v1/bookmarks/1
curl -s http://127.0.0.1:8000/api/v1/bookmarks/999
curl -s http://127.0.0.1:8000/health

请求预期
size=2 items 长 2,total=3
q=OpenAPI 命中相关书签
id=1 完整 BookmarkOut
id=999 404 + detail
/docs 分组与字段说明
openapi.json 含 paths 与 PageOut

清单: title/version、description 约定、tags、Query 描述、404、Schema 齐全。


10. 能力总表与自我检查

跑通后对照:约定是否统一、文档是否增强、json 能否导出。查「可协作」而非仅「200」。

用途做法
给人 /docs /redoc
给工具 导出 openapi.json
给前端 对接说明+稳定字段
给演进 /api/v1 版本
给测试 下节断言锁契约
  • 说清 OpenAPI 与契约
  • 分页四字段统一
  • 错误以 detail 为主
  • tags + Field 已写
  • 路径在 /api/v1
  • 会导出 openapi.json
  • 知破坏性要版本
  • 示例无密钥

总结

从「生成能点」到「约定可协作」,要把分页、错误、版本与字段说明当产品。OpenAPI 是机器契约;/docs 给人;openapi.json 给工具。Bookmark API 统一形状并挂 /api/v1,联调成本会下降。下节用测试锁住形状。

  • OpenAPI 描述 API;契约是双方同意的形状与行为。
  • 代码生成规范;tags/Field/response_model 决定可读性。
  • 分页统一 items/total/page/size;错误优先 detail。
  • 破坏性变更用版本或兼容期。
  • 导出 openapi.json;示例用假数据;以运行中规范为准。

小练笔

契约已从约定表落到可运行服务。先独立作答;建议对照 /docs 与 openapi.json。

题 1

/openapi.json 给谁用?举两类消费者。

题 2

为何分页字段要全项目统一?

题 3

破坏性删除响应字段时如何版本化?

题 4

判断:有 /docs 就等于前端无需其它说明。(对/错)

题 5

写出分页成功 JSON 骨架(items/total/page/size)。

题 6

Field(description/examples) 比只写 title: str 多了什么?

题 7

为何业务接口建议 /api/v1?

题 8

详情找不到书签时的状态码与 body?

题 9

哪项是兼容变更?A 删 url B 新增可选 tags C id 改 string

题 10(可选)

导出 openapi.json,确认含 /api/v1/bookmarks 与 PageOut。


小练笔参考答案

先做后看。

题 1

工具导入(Postman/Apifox)、生成客户端/SDK、契约校验等。

题 2

前端组件与 SDK 可复用,降联调成本。

题 3

升主版本或 /api/v2,给迁移/兼容期并更新文档。

题 4

题 5

{"items":[],"total":0,"page":1,"size":10}

题 6

人类可读描述与示例,文档更清晰。

题 7

破坏性可并存新版本;网关与路由好切分。

题 8

404,{"detail":"bookmark not found"}

题 9

B

题 10

文件中能搜到路径与 PageOut。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 小白python入门 - 50. OpenAPI 与接口契约
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!