请求体 - 更新数据

PUT 更新数据

更新数据请用 HTTP PUT 操作。

把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 jsonable_encoder。例如,把 datetime 转换为 str

  1. from typing import List, Optional
  2. from fastapi import FastAPI
  3. from fastapi.encoders import jsonable_encoder
  4. from pydantic import BaseModel
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: Optional[str] = None
  8. description: Optional[str] = None
  9. price: Optional[float] = None
  10. tax: float = 10.5
  11. tags: List[str] = []
  12. items = {
  13. "foo": {"name": "Foo", "price": 50.2},
  14. "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
  15. "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
  16. }
  17. @app.get("/items/{item_id}", response_model=Item)
  18. async def read_item(item_id: str):
  19. return items[item_id]
  20. @app.put("/items/{item_id}", response_model=Item)
  21. async def update_item(item_id: str, item: Item):
  22. update_item_encoded = jsonable_encoder(item)
  23. items[item_id] = update_item_encoded
  24. return update_item_encoded

PUT 用于接收替换现有数据的数据。

关于更新数据的警告

PUT 把数据项 bar 更新为以下内容时:

  1. {
  2. "name": "Barz",
  3. "price": 3,
  4. "description": None,
  5. }

因为上述数据未包含已存储的属性 "tax": 20.2,新的输入模型会把 "tax": 10.5 作为默认值。

因此,本次操作把 tax 的值「更新」为 10.5

PATCH 进行部分更新

HTTP PATCH 操作用于更新 部分 数据。

即,只发送要更新的数据,其余数据保持不变。

笔记

PATCH 没有 PUT 知名,也怎么不常用。

很多人甚至只用 PUT 实现部分更新。

FastAPI 对此没有任何限制,可以随意互换使用这两种操作。

但本指南也会分别介绍这两种操作各自的用途。

使用 Pydantic 的 exclude_unset 参数

更新部分数据时,可以在 Pydantic 模型的 .dict() 中使用 exclude_unset 参数。

比如,item.dict(exclude_unset=True)

这段代码生成的 dict 只包含创建 item 模型时显式设置的数据,而不包括默认值。

然后再用它生成一个只含已设置(在请求中所发送)数据,且省略了默认值的 dict

  1. from typing import List, Optional
  2. from fastapi import FastAPI
  3. from fastapi.encoders import jsonable_encoder
  4. from pydantic import BaseModel
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: Optional[str] = None
  8. description: Optional[str] = None
  9. price: Optional[float] = None
  10. tax: float = 10.5
  11. tags: List[str] = []
  12. items = {
  13. "foo": {"name": "Foo", "price": 50.2},
  14. "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
  15. "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
  16. }
  17. @app.get("/items/{item_id}", response_model=Item)
  18. async def read_item(item_id: str):
  19. return items[item_id]
  20. @app.patch("/items/{item_id}", response_model=Item)
  21. async def update_item(item_id: str, item: Item):
  22. stored_item_data = items[item_id]
  23. stored_item_model = Item(**stored_item_data)
  24. update_data = item.dict(exclude_unset=True)
  25. updated_item = stored_item_model.copy(update=update_data)
  26. items[item_id] = jsonable_encoder(updated_item)
  27. return updated_item

使用 Pydantic 的 update 参数

接下来,用 .copy() 为已有模型创建调用 update 参数的副本,该参数为包含更新数据的 dict

例如,stored_item_model.copy(update=update_data)

  1. from typing import List, Optional
  2. from fastapi import FastAPI
  3. from fastapi.encoders import jsonable_encoder
  4. from pydantic import BaseModel
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: Optional[str] = None
  8. description: Optional[str] = None
  9. price: Optional[float] = None
  10. tax: float = 10.5
  11. tags: List[str] = []
  12. items = {
  13. "foo": {"name": "Foo", "price": 50.2},
  14. "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
  15. "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
  16. }
  17. @app.get("/items/{item_id}", response_model=Item)
  18. async def read_item(item_id: str):
  19. return items[item_id]
  20. @app.patch("/items/{item_id}", response_model=Item)
  21. async def update_item(item_id: str, item: Item):
  22. stored_item_data = items[item_id]
  23. stored_item_model = Item(**stored_item_data)
  24. update_data = item.dict(exclude_unset=True)
  25. updated_item = stored_item_model.copy(update=update_data)
  26. items[item_id] = jsonable_encoder(updated_item)
  27. return updated_item

更新部分数据小结

简而言之,更新部分数据应:

  • 使用 PATCH 而不是 PUT (可选,也可以用 PUT);
  • 提取存储的数据;
  • 把数据放入 Pydantic 模型;
  • 生成不含输入模型默认值的 dict (使用 exclude_unset 参数);
    • 只更新用户设置过的值,不用模型中的默认值覆盖已存储过的值。
  • 为已存储的模型创建副本,用接收的数据更新其属性 (使用 update 参数)。
  • 把模型副本转换为可存入数据库的形式(比如,使用 jsonable_encoder)。
    • 这种方式与 Pydantic 模型的 .dict() 方法类似,但能确保把值转换为适配 JSON 的数据类型,例如, 把 datetime 转换为 str
  • 把数据保存至数据库;
  • 返回更新后的模型。
  1. from typing import List, Optional
  2. from fastapi import FastAPI
  3. from fastapi.encoders import jsonable_encoder
  4. from pydantic import BaseModel
  5. app = FastAPI()
  6. class Item(BaseModel):
  7. name: Optional[str] = None
  8. description: Optional[str] = None
  9. price: Optional[float] = None
  10. tax: float = 10.5
  11. tags: List[str] = []
  12. items = {
  13. "foo": {"name": "Foo", "price": 50.2},
  14. "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
  15. "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
  16. }
  17. @app.get("/items/{item_id}", response_model=Item)
  18. async def read_item(item_id: str):
  19. return items[item_id]
  20. @app.patch("/items/{item_id}", response_model=Item)
  21. async def update_item(item_id: str, item: Item):
  22. stored_item_data = items[item_id]
  23. stored_item_model = Item(**stored_item_data)
  24. update_data = item.dict(exclude_unset=True)
  25. updated_item = stored_item_model.copy(update=update_data)
  26. items[item_id] = jsonable_encoder(updated_item)
  27. return updated_item

提示

实际上,HTTP PUT 也可以完成相同的操作。 但本节以 PATCH 为例的原因是,该操作就是为了这种用例创建的。

笔记

注意,输入模型仍需验证。

因此,如果希望接收的部分更新数据可以省略其他所有属性,则要把模型中所有的属性标记为可选(使用默认值或 None)。

为了区分用于更新所有可选值的模型与用于创建包含必选值的模型,请参照更多模型 一节中的思路。