公司动态

小白python入门 - 42. 请求体与 Pydantic 模型

📅 2026/7/27 6:45:58
小白python入门 - 42. 请求体与 Pydantic 模型
1. 本课定位是什么、为何重要上一课书签 API 已经能「读列表、读详情、删除」参数来自路径和查询串。可是创建、更新资源时客户端通常要发一整段结构化 JSON——标题、URL、是否收藏——而不是把长字段都塞进 Query。手写dict.get东一块西一块很容易漏校验也容易在响应里把内部字段一起泄漏出去。本课引入Pydantic 模型与 FastAPI 的 Body 绑定用类描述数据形状框架负责解析、校验、生成文档并用 Create / Update / Out 分模型把「入库字段」和「对外字段」拆开。数据仍用内存存储。学完你应能 POST 创建、PATCH 部分更新读懂 422 的loc并确认internal_score不会出现在响应里。概念一句话请求体 BodyHTTP 正文里的载荷API 里多为 JSON模型 ModelBaseModel子类字段 类型 约束response_model规定响应长什么样避免内部字段漏出exclude_unset部分更新时只应用客户端真正传了的字段为何重要无边界模型 脏数据入库 敏感字段出站。对比已学已学本课Path/Query 简单类型Body 嵌套结构内存里直接塞 dictCreate/Update/Out分模型422 来自路径类型错422 也可来自 Body 校验失败只有读删加上创建与部分更新2. 本质入站校验、出站过滤可以把模型想成海关入关检查护照字段全不全、格式对不对出关只盖允许出境的章内部备注条不给外人看。FastAPI 在进路由函数前完成入站解析在返回时按response_model再滤一遍。上一课失败分 404/422本课 422 会更常见——空标题、非法 URL、类型不对都会在边界被拦住。先建立「边界合同」直觉再拆三个模型避免一个类打天下。本质JSON → 模型实例失败 422→ 业务用属性 → 按 Out 序列化响应。客户端 JSON | v Pydantic 校验 ----失败--- 422 loc/msg | 成功 v 路由函数业务逻辑 | v response_model 过滤 ----- 响应 JSON3. 约束与常见坑模型用错比不用更危险一个模型既当创建又当更新会导致「更新必须传全字段」或「响应带回哈希」。部分更新若model_dump()不带exclude_unset没传的字段可能变成 None 覆盖原值。这一节把分模型、exclude_unset、response_model 过滤语义钉死。注意过滤输出 ≠ 修改内存对象——internal_score仍可在 store 里只是不返回。约束Create / Update / Out 分开——入库字段 ≠ 对外字段。HttpUrl、Field(min_length...)等在边界拦住脏数据。部分更新用exclude_unsetTrue避免「没传的字段被写成 null」。响应模型会丢掉未声明字段如internal_score。Content-Type 需为application/jsoncurl 要带 Header。常见坑坑现象正确直觉一个模型打天下更新被迫传全字段或泄露敏感字段拆 Create/Update/Out更新用全量 dump未传字段变Nonemodel_dump(exclude_unsetTrue)把内部 dict 当 API 契约字段漂移、泄密明确 Out 模型忽略 422 的loc不知道哪错读loc/msg忘记 Content-Type解析失败或怪错JSON Header以为 response_model 会删内存字段调试以为「存没了」只影响序列化输出4. 三种模型对照Create 描述「新建必须提供什么」Update 字段全可选服务「改一点」Out 描述「客户端永远只能看见什么」。三者字段可以重叠但职责不同。书签域字段保持简单title、url、is_favorite外加 Out 的 id。内部internal_score只存在 store不进 Out。模型方向典型字段BookmarkCreate客户端 → 服务端title, url, is_favoriteBookmarkUpdate客户端 → 服务端字段全可选BookmarkOut服务端 → 客户端id 安全字段frompydanticimportBaseModel,Field,HttpUrlclassBookmarkCreate(BaseModel):title:strField(min_length1,max_length200)url:HttpUrl is_favorite:boolFalseclassBookmarkUpdate(BaseModel):title:str|NoneField(defaultNone,min_length1,max_length200)url:HttpUrl|NoneNoneis_favorite:bool|NoneNoneclassBookmarkOut(BaseModel):id:inttitle:strurl:stris_favorite:bool写法vs说明参数body: BookmarkCreateQueryq: str前者吃 Body后者吃查询串response_modelOut裸return dict前者锁定对外形状Field约束业务里if边界校验优先模型5. 方法与能力按用途归组不要求背完整 Pydantic 文档。入门会这几类能力即可约束、导出、响应声明、嵌套了解、从 ORM 构造下下课。表格当抽屉需要时知道去哪找而不是一次记光所有 API。用途能力约束Field、HttpUrl邮箱类需额外包了解导出model_dump()/model_dump(exclude_unsetTrue)响应response_model...、status_code201从 ORMOut 上model_config {from_attributes: True}第 44 课嵌套字段类型为另一个BaseModel或list[...]HttpUrl 注意入站是 URL 类型写入 dict/存储时常str(body.url)。6. POST 与 PATCH 语义POST 创建成功常用201响应体是新建资源Out。PATCH 部分更新只传要改的字段。PUT 全量替换本课不展开避免和 PATCH 搅在一起。状态码与模型课绑定没有 Body 模型时创建接口几乎写不稳。方法典型用途本课状态码POST/bookmarks新建201 OutPATCH/bookmarks/{id}改部分字段200 OutGET读可加 response_model200DELETE删204上节修改前无模型修改后手动 if 检查 titleField min_lengthreturn 全量 dictresponse_model 滤掉内部字段PATCH 覆盖成 Noneexclude_unset 只改传入项7. 阅读 422 响应校验失败时FastAPI 返回的 JSON 里detail常是列表每项有loc、msg、type。会读loc就能快速定位是 body 的哪个字段错了。这是联调基本功不要只看「失败了」要看「哪里失败」。字段含义loc错误位置如[body,title]msg人话/校验信息type错误类型代码空 title 实验预期状态码 422loc含title。8. 落地场景书签创建与收藏切换场景仍是书签 API新建一条、只改是否收藏、确认内部评分永不返回。内存 store 用自增 id重启丢失——与 41 课一致。把「防泄密」当成功能需求而不是可选美化。场景做法新建书签POST Create → 201 Out改是否收藏PATCH Update只传is_favorite防泄密internal_score不进 Out非法标题422不进 store9. 小步示例response_model 过滤用最小片段理解「内存有、响应无」。综合实践会把片段拼成完整项目。item{id:1,title:Example,url:https://example.com,is_favorite:True,internal_score:42,}# 若 response_modelBookmarkOut响应不含 internal_score位置internal_score_store[1]可以有HTTP 响应 JSON不应有10. 环境准备依赖与 41 课相同本课增加schemas.py。Windows 推荐 Cygwin/WSL 跑 heredoc。mkdir-p~/python-lab/src/day42/routerscd~/python-lab/src/day42 pipinstallfastapi0.110uvicorn[standard]0.2711. 综合实践完整可运行脚本写入 schemas、router、main启动后用三条 curl合法创建、非法 title、PATCH 只改收藏。对照表检查状态码与字段。服务占前台时另开终端。Windows 用 Cygwin/WSL 或手建文件。mkdir-p~/python-lab/src/day42/routerscd~/python-lab/src/day42catschemas.pyEOF from pydantic import BaseModel, Field, HttpUrl class BookmarkCreate(BaseModel): title: str Field(min_length1, max_length200) url: HttpUrl is_favorite: bool False class BookmarkUpdate(BaseModel): title: str | None Field(defaultNone, min_length1, max_length200) url: HttpUrl | None None is_favorite: bool | None None class BookmarkOut(BaseModel): id: int title: str url: str is_favorite: bool EOFcatrouters/bookmarks.pyEOF from fastapi import APIRouter, HTTPException from schemas import BookmarkCreate, BookmarkOut, BookmarkUpdate router APIRouter(prefix/bookmarks, tags[bookmarks]) _store: dict[int, dict] {} _next_id 1 router.get(, response_modellist[BookmarkOut]) def list_bookmarks(): return list(_store.values()) router.post(, response_modelBookmarkOut, status_code201) def create_bookmark(body: BookmarkCreate): global _next_id item { id: _next_id, title: body.title, url: str(body.url), is_favorite: body.is_favorite, internal_score: 42, } _store[_next_id] item _next_id 1 return item router.get(/{bookmark_id}, response_modelBookmarkOut) def get_bookmark(bookmark_id: int): item _store.get(bookmark_id) if not item: raise HTTPException(status_code404, detailbookmark not found) return item router.patch(/{bookmark_id}, response_modelBookmarkOut) def update_bookmark(bookmark_id: int, body: BookmarkUpdate): item _store.get(bookmark_id) if not item: raise HTTPException(status_code404, detailbookmark not found) data body.model_dump(exclude_unsetTrue) if url in data and data[url] is not None: data[url] str(data[url]) item.update(data) return item router.delete(/{bookmark_id}, status_code204) def delete_bookmark(bookmark_id: int): if bookmark_id not in _store: raise HTTPException(status_code404, detailbookmark not found) del _store[bookmark_id] return None EOFcatrouters/__init__.pyEOF EOF cat main.py EOF from fastapi import FastAPI from routers import bookmarks app FastAPI(titleDay42 Bookmark API, version0.1.0) app.include_router(bookmarks.router) app.get(/health) def health(): return {status: ok} EOFuvicorn main:app--reload--host127.0.0.1--port8000验证curl-s-XPOST http://127.0.0.1:8000/bookmarks\-HContent-Type: application/json\-d{title:Example,url:https://example.com,is_favorite:true}curl-s-XPOST http://127.0.0.1:8000/bookmarks\-HContent-Type: application/json\-d{title:,url:https://example.com}curl-s-XPATCH http://127.0.0.1:8000/bookmarks/1\-HContent-Type: application/json\-d{is_favorite:false}curl-shttp://127.0.0.1:8000/bookmarks/1预期请求结果合法 POST201JSON无internal_score空 title422loc含body/titlePATCH只改is_favoritetitle 仍在GET 详情仍无internal_score关键语义response_model过滤输出 ≠ 删除内存里的internal_score。12. 嵌套与列表了解真实书签可能带 tags 列表或 owner 嵌套对象。入门知道「字段类型可以是 list 或另一个 BaseModel」即可本课作业不强制嵌套。列表响应可用response_modellist[BookmarkOut]综合实践已示范。classTag(BaseModel):name:strclassBookmarkCreateNested(BaseModel):title:strurl:HttpUrl tags:list[Tag][]13. 常见问答QCreate 和 Out 都有 title为何还要两个类AOut 需要 idCreate 不应让客户端指定 id未来 Out 还可能隐藏更多字段。QUpdate 全是 Optional 会不会太松APATCH 语义就是可选可用业务规则要求「至少改一个字段」进阶。Q非法 URL 是 422 还是 400APydantic/FastAPI 校验失败通常 422。Q能直接return body吗ACreate 没有 id应构造带 id 的资源再按 Out 返回。14. 自我检查清单能解释 Body 与 Query 的差别会写 Create/Update/Out 三个模型会 POST 201 response_model会 PATCH exclude_unset会读 422 的 loc确认 internal_score 不出现在响应curl 带 Content-Type: application/json15. 与前后课衔接课关系41Path/Query/读删 → 本课 Body 写43模型稳定 → 依赖与配置分层44dict store → ORMOut 加 from_attributes总结带走Body 用模型校验Create/Update/Out 分离422 读 locresponse_model 防泄密部分更新 exclude_unset。模型是边界合同不是数据库表的镜像。请求体用 Pydantic 在边界校验。Create / Update / Out 分模型避免一个类打天下。response_model锁定对外形状过滤内部字段。PATCH 用model_dump(exclude_unsetTrue)做部分更新。校验失败读loc/msg空 title、坏 URL 多为 422。HttpUrl入库时常转为str内存仍可有内部字段。小练笔先做再看答案。可选实践故意漏掉 url 字段观察 422 loc。题 1response_modelBookmarkOut的作用题 2为何密码哈希不该出现在 UserOut题 3exclude_unsetTrue解决什么问题题 4空 title 更可能得到 404 还是 422题 5为「只改 url」设计 Update 请求 JSON 示例。题 6POST 创建成功更常见状态码A. 200 B. 201 C. 204题 7判断response_model 会从数据库/内存里物理删除未声明字段。题 8curl POST JSON 时为什么常需要Content-Type: application/json题 9可选实践POST 一条合法书签后响应 JSON 中搜索internal_score应找不到。题 10Create 模型里应不应该包含id字段让客户端指定为什么小练笔参考答案题 1按 Out 过滤/校验响应并写入 OpenAPI。题 2敏感内部数据返回即泄露。题 3部分更新时只应用客户端真正传入的字段。题 4422题 5{url:https://new.example}其它字段不传。题 6B题 7错主要影响序列化输出题 8声明正文是 JSON便于框架正确解析 Body。题 9以你运行为准响应不应出现该键。题 10一般不应id 由服务端分配避免冲突与伪造。合理即可