FastAPI 生成 TypeScript SDK 实战从 OpenAPI 规范到客户端代码与自定义 operationId【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapiFastAPI 基于 OpenAPI 规范自动生成接口描述这意味着你无需手写客户端就能为任意 FastAPI 后端生成带自动补全和内联类型检查的多语言 SDK软件开发工具包。本文以官方文档《Generating SDKs》为主线完整演示如何用一个简单的 FastAPI 应用生成 TypeScript 客户端并深入源码剖析generate_unique_id_function参数——它决定了 OpenAPI 中 operation ID进而决定生成客户端的方法名是如何产出的读完你可以掌握一套“后端即前端客户端”的可维护开发工作流。OpenAPI 规范是 SDK 生成的基石FastAPI构建在OpenAPI规范之上所有 API 都会被描述为一种标准格式大量工具可以直接消费这份格式。由此可以自动生成始终与代码保持同步的文档多种语言的客户端库SDK与后端保持同步的测试与自动化流程。开源 SDK 生成器选型工具定位OpenAPI Generatoropenapi-generator.tech通用方案支持众多编程语言从 OpenAPI 规范生成 SDKHey APIheyapi.dev面向TypeScript 客户端的专用方案为 TypeScript 生态做了深度优化OpenAPI.ToolsSDK 生成器目录站可发现更多同类工具以上工具名称均为公开项目具体安装与产物说明可查阅各自官网文档。重要前提FastAPI 自动生成的规范版本是OpenAPI 3.1因此你选用的任何生成器都必须支持该版本。这一点在仓库测试快照中可以直接验证tests/test_generate_unique_id_function.py 中对/openapi.json响应的断言显示openapi: 3.1.0。构建示例应用模型驱动 schema 生成一切从 docs_src/generate_clients/tutorial001_py310.py 这个最简单的 FastAPI 应用开始from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Item(BaseModel): name: str price: float class ResponseMessage(BaseModel): message: str app.post(/items/, response_modelResponseMessage) async def create_item(item: Item): return {message: item received} app.get(/items/, response_modellist[Item]) async def get_items(): return [ {name: Plumbus, price: 3}, {name: Portal Gun, price: 9001}, ]注意这里的path operations路径操作通过Item和ResponseMessage两个 Pydantic 模型声明了请求体与响应体的类型。模型如何变成 API 文档中的 schemas访问/docs会看到 Swagger UI 中列出了请求要发送、响应要接收的数据schemas——这些 schema 正是由应用内声明的模型产生的。其信息流是Pydantic 模型Item、ResponseMessage被声明在 path operation 上FastAPI 将模型信息写入应用的OpenAPI schema/openapi.jsonAPI 文档/docs从中读取并展示同一份OpenAPI 中的模型信息就是后续生成客户端代码的输入。也就是说后端模型即客户端类型的“单一事实来源”。用 Hey API 生成 TypeScript 客户端应用带模型就绪后最快的生成方式是通过 npx 调用 Hey APInpx hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client-i指定输入可以是运行中应用的 OpenAPI JSON 地址也可以是本地文件-o指定输出目录这里生成的 TypeScript SDK 落在./src/client。使用 SDK补全与内联错误生成后即可导入使用核心体验包括方法自动补全调用客户端时所有由 path operations 生成的方法名都会出现在补全列表中请求体补全发送 payload 时也能补全。例如name和price这两个字段并非生成器编造而是来自 FastAPI 应用中Item模型的定义内联错误对发送的数据不合法时编辑器直接给出行内报错响应对象补全返回对象同样有完整的类型提示。后端改字段客户端类型自动跟着变——这是手写客户端无法比拟的同步能力。更大的应用用 tags 分组并生成结构化客户端真实项目中 FastAPI 应用会更大通常会用tags把不同组的 path operations 分开例如items一组、users一组。见 docs_src/generate_clients/tutorial002_py310.pyfrom fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Item(BaseModel): name: str price: float class ResponseMessage(BaseModel): message: str class User(BaseModel): username: str email: str app.post(/items/, response_modelResponseMessage, tags[items]) async def create_item(item: Item): return {message: Item received} app.get(/items/, response_modellist[Item], tags[items]) async def get_items(): return [ {name: Plumbus, price: 3}, {name: Portal Gun, price: 9001}, ] app.post(/users/, response_modelResponseMessage, tags[users]) async def create_user(user: User): return {message: User received}为这个带 tags 的应用生成客户端后客户端代码通常也会按 tag 分组ItemsServiceUsersService这样客户端代码的分组、组织与后端结构一一对应。默认方法名为什么“丑”operation ID 的生成规则此时生成的方法名类似ItemsService.createItemItemsPost({name: Plumbus, price: 5})createItemItemsPost并不美观。原因可以从源码层面说清楚客户端生成器对每个 path operation 使用的是 OpenAPI 内部的operation ID而 OpenAPI 要求所有 operation ID 全局唯一。为保证唯一性FastAPI 默认用函数名 路径 HTTP 方法来拼出 operation ID。默认实现位于 fastapi/utils.pydef generate_unique_id(route: APIRoute) - str: operation_id f{route.name}{route.path_format} operation_id re.sub(r\W, _, operation_id) assert route.methods operation_id f{operation_id}_{list(route.methods)[0].lower()} return operation_id逐行解读route.name即函数名如create_item与route.path_format如/items/直接拼接用正则re.sub(r\W, _, ...)把路径中的非单词字符/替换为下划线再追加第一个 HTTP 方法的小写形式如post。于是create_item/items/post就成了create_item_items__post客户端生成器再按驼峰等规则改写最终呈现出createItemItemsPost这种冗长名称。自定义 Operation IDgenerate_unique_id_function 参数你可以自定义operation ID 的生成方式让客户端获得更简单的方法名——但此时需要由你自行保证每个 operation ID 的唯一性。一个稳妥策略确保每个 path operation 都有 tag然后按tag 函数名生成 ID。自定义 generate_unique_id 函数FastAPI 为每个 path operation 生成一个unique ID它同时用于operation ID以及请求/响应所需的自定义模型的命名后文会用测试快照佐证。generate_unique_id_function参数的签名是“接收一个APIRoute返回一个字符串”。完整示例见 docs_src/generate_clients/tutorial003_py310.pyfrom fastapi import FastAPI from fastapi.routing import APIRoute from pydantic import BaseModel def custom_generate_unique_id(route: APIRoute): # 取第一个 tag通常每个操作只会有一个 tag 函数名 return f{route.tags[0]}-{route.name} # 把自定义函数传给 FastAPI app FastAPI(generate_unique_id_functioncustom_generate_unique_id) class Item(BaseModel): name: str price: float class ResponseMessage(BaseModel): message: str class User(BaseModel): username: str email: str app.post(/items/, response_modelResponseMessage, tags[items]) async def create_item(item: Item): return {message: Item received} app.get(/items/, response_modellist[Item], tags[items]) async def get_items(): return [ {name: Plumbus, price: 3}, {name: Portal Gun, price: 9001}, ] app.post(/users/, response_modelResponseMessage, tags[users]) async def create_user(user: User): return {message: User received}源码中的参数定义与测试佐证该参数在FastAPI类中的定义与文档说明见 fastapi/applications.pygenerate_unique_id_function: Annotated[ Callable[[routing.APIRoute], str], Doc( Customize the function used to generate unique IDs for the *path operations* shown in the generated OpenAPI. This is particularly useful when automatically generating clients or SDKs for your API. ... ), ] Default(generate_unique_id),注意默认值就是前文剖析的generate_unique_id来自fastapi/utils.py并且参数文档明确写着“自动生成客户端或 SDK 时特别有用”。从源码结构看APIRouter同样暴露了该参数见 fastapi/routing.py 中多处generate_unique_id_function的传递链因此自定义函数既可以在FastAPI顶层配置也可以随include_router的上下文继承机制向下传递。再看仓库测试 tests/test_generate_unique_id_function.py 如何验证这一机制def custom_generate_unique_id(route: APIRoute): return ffoo_{route.name} def test_top_level_generate_unique_id(): app FastAPI(generate_unique_id_functioncustom_generate_unique_id) router APIRouter() # ...注册 / 和 /router 两个 post 操作后... client TestClient(app) response client.get(/openapi.json) assert response.json() snapshot( { openapi: 3.1.0, ... operationId: foo_post_root, requestBody: { content: { application/json: { schema: { $ref: #/components/schemas/Body_foo_post_root } } }, required: True, }, ... } )这个测试快照印证了两个关键事实自定义函数生效后operationId变为foo_post_rootfoo_前缀 函数名路径与 HTTP 方法信息消失请求体的$ref指向Body_foo_post_root——证明unique ID 不仅用于 operation ID还参与请求/响应包装模型的命名这正是文档中“用于 requests 或 responses 所需的自定义模型命名”的源码级证据。再次生成客户端后方法名就变成了tag 函数名的组合不再包含 URL 路径和 HTTP 操作信息。权衡为 OpenAPI 保留 tag 前缀此时生成的代码里仍有重复信息方法既然已经归属ItemsService来自 tag方法名里还带items-前缀就显得冗余。合理的取舍是OpenAPI 规范层面保留前缀以维持 operation ID 全局唯一但在生成客户端之前把 OpenAPI 里的 operation ID 前缀去掉让生成器产出更干净的方法名。预处理 OpenAPI 规范去掉 tag 前缀把 OpenAPI JSON 下载为本地文件openapi.json后可以用脚本批量去掉 operation ID 的 tag 前缀。Python 版脚本见 docs_src/generate_clients/tutorial004_py310.pyimport json from pathlib import Path file_path Path(./openapi.json) openapi_content json.loads(file_path.read_text()) for path_data in openapi_content[paths].values(): for operation in path_data.values(): tag operation[tags][0] operation_id operation[operationId] to_remove f{tag}- new_operation_id operation_id[len(to_remove):] operation[operationId] new_operation_id file_path.write_text(json.dumps(openapi_content))逻辑很短遍历paths下每个 HTTP 操作取第一个 tag把operationId的{tag}-前缀切掉后写回。例如items-get_items会被改名为get_items客户端生成器随之产出更简洁的方法名。Node.js 版本对不匹配前缀的操作做了防御性判断见 docs_src/generate_clients/tutorial004.jsimport * as fs from fs async function modifyOpenAPIFile(filePath) { try { const data await fs.promises.readFile(filePath) const openapiContent JSON.parse(data) const paths openapiContent.paths for (const pathKey of Object.keys(paths)) { const pathData paths[pathKey] for (const method of Object.keys(pathData)) { const operation pathData[method] if (operation.tags operation.tags.length 0) { const tag operation.tags[0] const operationId operation.operationId const toRemove ${tag}- if (operationId.startsWith(toRemove)) { const newOperationId operationId.substring(toRemove.length) operation.operationId newOperationId } } } } await fs.promises.writeFile( filePath, JSON.stringify(openapiContent, null, 2), ) console.log(File successfully modified) } catch (err) { console.error(Error:, err) } } const filePath ./openapi.json modifyOpenAPIFile(filePath)用本地文件重新生成客户端由于输入源变成了本地文件需要更新-i参数npx hey-api/openapi-ts -i ./openapi.json -o src/client重新生成后你就拥有了干净的方法名且保留全部自动补全与内联错误能力。生成式客户端的收益采用自动生成的客户端你会获得自动补全覆盖三类对象客户端方法、请求载荷body、query 参数等、响应载荷所有数据操作的内联类型错误提示持续同步每次更新后端代码并重新生成前端客户端后新增的 path operations 会作为新方法出现、已删除的会消失、任何变更都会体现在生成代码中。更深层的价值在于错误左移如果后端与客户端数据不一致构建阶段就会报错而不是等错误暴露给生产环境的最终用户再去艰难调试。这意味着大量类型与契约错误能在开发生命周期最早期被发现并修复。小结FastAPI 自动产出OpenAPI 3.1规范/openapi.json这是文档、客户端、测试三类工具的共同数据源模型声明请求/响应 Pydantic 模型tags分组直接决定了生成客户端的结构ItemsService/UsersService与方法名FastAPI(generate_unique_id_function...)让你接管 operation ID 的生成规则默认规则是“函数名 路径 HTTP 方法”fastapi/utils.py自定义时务必自行保证唯一性unique ID 同时参与 operation ID 与请求/响应包装模型命名测试快照 tests/test_generate_unique_id_function.py 中的Body_foo_post_root可证在规范层面保留唯一性前缀、在生成客户端前用脚本Python/Node.js 均可剥离前缀是兼顾 OpenAPI 规范合规与客户端方法名美观的实用做法。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考