ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【FDE系列】阶段2:Day 29:FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战

【FDE系列】阶段2:Day 29:FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战 前言FDE系列内容总纲【大纲】FDE 前沿部署工程师学习系列教程-CSDN博客前置课程列表阶段一【FDE系列】阶段1Day 1AI 层级关系 — 四个嵌套的圈-CSDN博客【FDE系列】阶段1Day 2AI 三阶段发展史 — 会认 → 会判断 → 会创造-CSDN博客【FDE系列】阶段1Day 3符号 AI vs 机器学习 — 两条路线的本质区别-CSDN博客【FDE系列】阶段1Day 4Transformer 的历史意义 — 2017 年的分水岭-CSDN博客【FDE系列】阶段1Day 5本周复习与自测 — 检验你的 AI 认知地基-CSDN博客【FDE系列】阶段1Day 6Transformer 架构 — 一张图纸盖出千千万万栋楼-CSDN博客【FDE系列】阶段1Day 7LLM 本质 — 文字接龙机器-CSDN博客【FDE系列】阶段1Day 8Token — 模型眼中的最小单位-CSDN博客【FDE系列】阶段1Day 9AI 幻觉 — 为什么会一本正经地胡说八道-CSDN博客【FDE系列】阶段1Day 10上下文窗口 — 模型的记忆力上限 本周复习-CSDN博客【FDE系列】阶段1Day 11Prompt — 给模型立规矩-CSDN博客【FDE系列】阶段1Day 12Memory — 让模型记住上下文【FDE系列】阶段1Day 13RAG — 给模型配图书管理员-CSDN博客【FDE系列】阶段1Day 14Tool Use — 让模型动手操作-CSDN博客【FDE系列】阶段1Day 15MCP — 统一的工具接口标准 第三周复习-CSDN博客【FDE系列】阶段1Day 16什么是 FDE — 把 AI 变成客户结果的人-CSDN博客【FDE系列】阶段1Day 17FDE vs 传统实施 — 三大本质区别-CSDN博客【FDE系列】阶段1Day 18FDE 三重身份 C6 胜任力模型-CSDN博客【FDE系列】阶段1Day 19七阶段行动路径 行业经验的价值-CSDN博客【FDE系列】阶段1Day 20阶段总结与产出物 — 第一阶段收官-CSDN博客阶段二【FDE系列】阶段2Day 21Python 环境搭建 — 写出你的第一行代码-CSDN博客【FDE系列】阶段2Day 22变量、数据类型、条件判断 — Python 的“记忆“和“判断“-CSDN博客【FDE系列】阶段2Day 23循环与函数 — 让代码跑 100 遍、把逻辑打包复用-CSDN博客【FDE系列】阶段2Day 24数据结构 — 列表、字典、集合、元组-CSDN博客【FDE系列】阶段2Day 25文件读写与 JSON — 让程序连通外部数据第一周收官-CSDN博客【FDE系列】阶段2Day 26模块化编程 — 把代码拆成“抽屉柜“-CSDN博客【FDE系列】阶段2Day 27异常处理与日志 — 让程序“摔不烂、查得到“-CSDN博客【FDE系列】阶段2Day 28FastAPI 入门 — 把你的函数变成 API 服务-CSDN博客阶段2·Day 29FastAPI 进阶 — Pydantic 模型与完整 CRUD 实战FDE 学习系列教程 · 第二阶段 · 第 2 周 · Day 4预计时长3 小时 | 难度★★★★☆ | 前置知识Day 28FastAPI 路由基础一句话目标用 Pydantic 模型定义数据契约实现工单的增删改查全套接口理解请求体、响应模型、状态码与分层结构——交付一个能完整演示的 API 服务。‍‍ 老哥开场白昨天的服务只能查。今天补齐增、改、删做一个完整的工单管理 API。但先解决一个新问题新增工单时调用方要发来一堆数据标题、优先级、描述、上报人……。这些数据放在哪怎么检查必填项漏没漏、优先级是不是合法值靠手写 if 判断一个接口 20 个字段时你会想死。答案是Pydantic——FastAPI 的数据校验底座。你用一个类声明数据长什么样校验、转换、文档、报错全部自动化。 今天是第二周最硬核的一天新概念三个请求体模型、响应模型、完整 CRUD 链路。难度上来了别慌今天的代码是一个整体跟着敲完再回头看每一块都很清楚。 请求体POST 发来的数据放哪GET 请求参数全在网址里。但新增一条工单要提交很多字段塞网址里既不安全也不现实——这些数据放在请求体Request Body里就是一段 JSON。调用方发来的东西长这样{ title: 注塑机A3温度报警, priority: 高, description: 温度持续超过90度, reporter: 赵工 }问题来了❌ title 没传怎么办 ❌ priority 传了个超级高这种瞎编的值怎么办 ❌ description 传成数字 123 怎么办Pydantic 出场。 Pydantic 模型给数据立规矩定义模型from fastapi import FastAPI from pydantic import BaseModel app FastAPI() # 继承 BaseModel声明工单应该长什么样 class TicketCreate(BaseModel): title: str # 标题必须是字符串且必填 priority: str 中 # 优先级字符串不传时默认中 description: str # 描述字符串可空 reporter: str # 上报人必填这个类同时干了四件事1. 校验 缺了 title类型不对→ 自动返回 422错误详情精确到字段 2. 转换 传 82 而字段声明 int→ 自动帮你转能转的前提下 3. 文档 /docs 页面自动展示这个 JSON 结构和示例 4. 提示 写代码时有自动补全点出 ticket.title 不担心拼错解说Pydantic 作用Pydantic 是一个基于Python 类型注解type hints的数据校验库。它的核心思想你用类型注解描述数据长什么样Pydantic 自动帮你校验和转换。BaseModel 是什么BaseModel是 Pydantic 里所有数据模型的基类。你定义自己的模型时继承它然后用类型注解声明字段。上面声明创建工单时客户端应该提交什么样的 JSON。字段类型是否必填说明titlestr必填没有默认值 → 必填prioritystr可选有默认值中descriptionstr可选默认reporterstr必填没有默认值 → 必填规律字段有没有默认值决定它是否必填。接收请求体POST 接口TICKETS [] # 先用内存列表当数据库 app.post(/tickets, status_code201) def create_ticket(ticket: TicketCreate): new_id len(TICKETS) 1 record {id: new_id, **ticket.model_dump(), status: 待处理} TICKETS.append(record) return record要点参数ticket: TicketCreate——FastAPI 看到参数是 Pydantic 模型就知道去请求体里取 JSON 并按规矩校验status_code201创建成功按 HTTP 惯例返回201 Created而不是普通 200ticket.model_dump()把模型转回字典Pydantic v2 写法v1 老项目里叫.dict()看到要认识即{title: ..., priority: ..., description: ..., reporter: ...}启动服务在/docs里测试找到POST /tickets→ Try it out请求体模板已经自动生成好改改值 → Execute试试故意删掉 title 字段再提交 → 收到精确的 422 报错{ detail: [{ type: missing, loc: [body, title], msg: Field required }] } 体会这个威力你只声明了数据的形状所有校验代码都是零行。这就是为什么声明式框架在工程上完胜手工校验。️ 字段约束让规矩更细光有类型不够业务规则更常见from pydantic import BaseModel, Field class TicketCreate(BaseModel): title: str Field(min_length2, max_length100) priority: str Field(default中, pattern^(高|中|低)$) description: str reporter: str Field(min_length1)约束效果min_length/max_length字符串长度区间pattern必须匹配正则这里只允许 高/中/低default默认值gt/lt/ge/le数字的 / / ≥ / ≤数字字段的例子设备上报场景class SensorReading(BaseModel): device_id: str temperature: float Field(ge-50, le200) # 合理温度区间 vibration: float Field(default0, ge0)传个temperature: 999→ 422 直接打回。昨天用raise ValueError手工防的范围问题今天在门口就被框架拦住了。这块确实硬核一次消化不了正常先记住Field 是加约束的地方实际用到时回来看这张表。 响应模型控制吐回去的数据接口返回也应该有规矩。两个典型诉求别泄露内部字段数据库里的内部备注、处理人手机号不能往外吐保证输出格式稳定调用方依赖你的字段结构不能时有时无用response_model解决class TicketResponse(BaseModel): id: int title: str priority: str status: str reporter: str # 故意不声明内部字段即使数据里有也不会返回 app.get(/tickets/{ticket_id}, response_modelTicketResponse) def get_ticket(ticket_id: int): for t in TICKETS: if t[id] ticket_id: return t # 内部字段会被自动过滤 raise HTTPException(status_code404, detail工单不存在)请求模型 vs 响应模型为什么分开定义 TicketCreate进门安检不要 id系统生成、不要 status默认待处理 TicketResponse出门着装要有 id 和 status但不暴露内部字段 进和出的规矩不一样所以是两个模型。️ FDE 实战工单管理完整 CRUD新建tickets_api/main.py一次写全五个接口工单管理 API完整 CRUD内存存储版 from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field app FastAPI(titleFDE 工单系统 API, version1.0.0) # ---------- 数据模型契约层---------- class TicketCreate(BaseModel): title: str Field(min_length2, max_length100) priority: str Field(default中, pattern^(高|中|低)$) description: str reporter: str Field(min_length1) class TicketUpdate(BaseModel): # 更新时全部可选——传啥改啥PATCH 语义 title: str | None Field(defaultNone, min_length2, max_length100) priority: str | None Field(defaultNone, pattern^(高|中|低)$) status: str | None Field(defaultNone, pattern^(待处理|处理中|已解决|已关闭)$) description: str | None None class TicketResponse(BaseModel): id: int title: str priority: str status: str description: str reporter: str # ---------- 内存数据库下周换成 MySQL---------- TICKETS: list[dict] [ {id: 1, title: 注塑机A3温度报警, priority: 高, status: 处理中, description: 温度91℃, reporter: 赵工}, {id: 2, title: 冲压机B1例行保养, priority: 低, status: 已解决, description: 已加注润滑油, reporter: 李工}, ] # ---------- CRUD 接口 ---------- # Create新增 app.post(/tickets, response_modelTicketResponse, status_code201, tags[工单]) def create_ticket(payload: TicketCreate): new_id max((t[id] for t in TICKETS), default0) 1 record {id: new_id, status: 待处理, description: , **payload.model_dump()} TICKETS.append(record) return record # Read列表带状态筛选 app.get(/tickets, tags[工单]) def list_tickets(status: str | None None, priority: str | None None): result TICKETS if status: result [t for t in result if t[status] status] if priority: result [t for t in result if t[priority] priority] return {count: len(result), items: result} # Read单条 app.get(/tickets/{ticket_id}, response_modelTicketResponse, tags[工单]) def get_ticket(ticket_id: int): for t in TICKETS: if t[id] ticket_id: return t raise HTTPException(404, detailf工单 {ticket_id} 不存在) # Update修改传啥改啥 app.put(/tickets/{ticket_id}, response_modelTicketResponse, tags[工单]) def update_ticket(ticket_id: int, payload: TicketUpdate): for t in TICKETS: if t[id] ticket_id: changes payload.model_dump(exclude_unsetTrue) # exclude_unsetTrue只有调用方真正传了的字段才在字典里 t.update(changes) return t raise HTTPException(404, detailf工单 {ticket_id} 不存在) # Delete删除 app.delete(/tickets/{ticket_id}, tags[工单]) def delete_ticket(ticket_id: int): for i, t in enumerate(TICKETS): if t[id] ticket_id: TICKETS.pop(i) return {message: f工单 {ticket_id} 已删除} raise HTTPException(404, detailf工单 {ticket_id} 不存在)全链路验收在 /docs 里按顺序点步骤操作预期1GET /tickets返回 2 条种子数据2POST /tickets传合法工单201id 自动变 3status 默认待处理3POST 时 priority 传特急422正则校验拦下4POST 时只传 reporter422提示 title 必填5GET /tickets/3看到刚创建的工单6PUT /tickets/3只传{status: 处理中}只改状态其他字段不变7GET /tickets?status处理中筛选生效8DELETE /tickets/3删除成功9再GET /tickets/3404 特别体会第 6 步的exclude_unsetTrue没有它调用方没传的字段会以 None 覆盖原数据工单标题瞬间消失。这个小参数是部分更新接口的灵魂。注意一个经典坑路径要排顺序如果同时有/tickets/{ticket_id}和/tickets/stats这种固定路径固定路径要写在动态路径前面否则stats会被当成{ticket_id}去匹配。现在先记住以后踩了不慌。️ 把昨天学的工程化用上单文件教学方便看真实项目应该按 Day 26 的方式分层。你的工单项目推荐长这样tickets_api/ ├── main.py # 入口创建 app、挂载路由 ├── models.py # Pydantic 模型数据契约 ├── services.py # 业务逻辑CRUD 操作数据的函数 ├── data_store.py # 数据存取现在是内存下周换 MySQL └── exceptions.py # 自定义异常可选分层的核心收益接口层只负责收发业务逻辑在 services 里数据在 data_store 里。下周把内存存储换成 SQL 数据库时只改data_store.py一个文件——接口和业务逻辑毫发无伤。这就是 Day 26职责分离在 Web 项目里的延续。一个示意不用全敲理解结构# services.py —— 业务逻辑不碰 FastAPI纯 Python可单独测试 def create_ticket(payload: dict) - dict: new_id ... record ... return record # main.py —— 薄薄一层只做 HTTP 翻译 app.post(/tickets, status_code201) def create_ticket(payload: TicketCreate): return ticket_service.create_ticket(payload.model_dump()) 记住这个判断标准如果你把 FastAPI 换成别的框架业务逻辑应该基本不用改。业务逻辑和框架绑死是新手项目最常见的架构问题。明天的 pytest 练习里你会尝到逻辑层独立带来的测试便利。 本课小结知识点一句话记住请求体POST/PUT 发来的 JSON用 Pydantic 模型接收BaseModel继承它定义数据形状自动校验文档补全Field加约束长度、正则、范围、默认值model_dump()模型转字典v1 老写法.dict()response_model控制输出字段防泄露、稳格式201 / 404创建成功 201资源不存在 404exclude_unset部分更新时只改调用方真正传了的字段CRUDPOST 增 / GET 查 / PUT 改 / DELETE 删分层接口层 / 业务层 / 数据层各管各的今天最核心的认知Pydantic 模型是接口的契约——进门按 Create 模型安检出门按 Response 模型着装。契约写清楚了前后端或两个系统就能并行开发、互不猜疑。FDE 对接客户 ERP/MES 时定义清楚数据契约永远是开工第一件事。 课后练习加字段给工单模型增加assignee处理人可选默认空字符串和tags字符串列表默认空列表提示list[str] []。更新响应模型并走一遍完整 CRUD。统计接口实现GET /tickets/stats/summary返回各状态工单数用 Day 24 的字典计数模式。注意路径顺序问题——把它放在{ticket_id}路由前面思考为什么。架构拆分选做把今天的单文件版本拆成main.py models.py services.py三个文件服务行为保持不变。拆完在/docs验证功能完全一致。 今日反思数据契约先行这个思路跟你做实施时的哪份文档最像接口确认单数据字段映射表今天的 5 个接口里哪个的边界情况最多比如更新不存在的工单、删除已删除的工单把你能想到的异常输入列一个清单——这就是明天写测试的素材。明天预告接口写完了怎么证明它一直是对的总不能每次上线前手动点 20 遍。明天学pytest 单元测试让代码自动验证代码、类型注解让 Bug 在敲代码时就暴露、配置管理.env 文件管理环境差异和requirements.txt依赖清单。学完明天你的项目就达到了可交付、可维护的门槛第二周圆满收官FDE 学习系列教程 · 第二阶段 · 第 2 周 Day 4 · 完
返回列表