(六)部分更新中的未提供、空值与假值
1. 用三份输入对照
书籍原来是 title="Python Web"、finished=True、tags=["网络"]。现在只想改为未读,发送 {"finished":false} 就够了。没有提交的书名和标签应继续保留。
| 本次输入 | 本次动作 |
|---|---|
{"finished":false} |
更新为未读,其他字段保留 |
{"tags":[]} |
清空标签,书名和状态保留 |
{"finished":null} |
返回 422,不修改记录 |
{} |
返回 422,提示提供要修改的字段 |
# 续写同一文件:api_project/src/bookapi/schemas.py
class BookPatch(InputModel):
# None 是“未提供”字段的默认占位;显式传 null 会在下面被拒绝。
title: Title | None = None
finished: Flag | None = None
tags: Tags | None = None
@field_validator("tags")
@classmethod
def unique_tags(cls, value: list[str] | None) -> list[str] | None:
return None if value is None else list(dict.fromkeys(value))
@model_validator(mode="after")
def check_changes(self) -> "BookPatch":
if not self.model_fields_set:
raise ValueError("至少提供一个需要修改的字段")
for name in self.model_fields_set:
if getattr(self, name) is None:
raise ValueError(f"{name} 不能是 null")
return selftitle: Title | None = None 让未提交字段能在构造时采用占位值。model_fields_set 保存调用者实际提交的字段名,因此能分辨“没有提交 finished”和“明确提交 finished=null”。模型级 @model_validator(mode="after") 在字段检查之后取得整个模型,再检查这些组合要求,成功时返回 self。
保存时使用 model_dump(exclude_unset=True)。unset 指没有显式赋值的字段,不是值为假的字段;False 与 [] 都会留在结果中。可以运行下面的独立例子观察:
# 项目文件:demos/validate_models.py
from pydantic import ValidationError
from bookapi.schemas import BookCreate, BookPatch
book = BookCreate.model_validate(
{"title": " Python Web ", "tags": [" 网络 ", "网络"]}
)
print(book.model_dump()) # 书名去空白,标签合并重复项。
for payload in [{"finished": False}, {"tags": []}, {"finished": None}, {}]:
try:
patch = BookPatch.model_validate(payload)
print(sorted(patch.model_fields_set), patch.model_dump(exclude_unset=True))
except ValidationError as exc:
print("校验失败", exc.errors()[0]["loc"])后面的写入代码不需要再猜每个字段“有没有值”,只需要依次应用这份明确的变化字典。