正如已经问过的similar有疑问,我要支持PATCH
FastApi 应用程序的操作,调用者可以根据需要指定 Pydantic 的任意数量的字段BaseModel
有子模型,这样高效PATCH
可以执行操作,而调用者不必仅仅为了更新两个或三个字段而提供整个有效模型。
我发现有2 steps在派丹提克PATCH
来自tutorial https://fastapi.tiangolo.com/tutorial/body-updates/#partial-updates-with-patch that 不支持子型号。然而,Pydantic 对我来说太好了,无法批评它似乎可以使用 Pydantic 提供的工具构建的东西。这个问题是要求实施这两件事同时还支持子型号:
- 生成一个新的DRY
BaseModel
所有字段都是可选的
- 通过更新实现深复制
BaseModel
Pydantic 已经认识到这些问题。
- 有讨论 https://github.com/pydantic/pydantic/discussions/3089可选模型的基于类的解决方案
- 还有那里two https://github.com/pydantic/pydantic/issues/4177 issues https://github.com/pydantic/pydantic/issues/3785在深拷贝上打开并更新
A similar question https://stackoverflow.com/q/67699451已经在这里被问过一两次了,并且有一些很好的答案,采用不同的方法来生成嵌套的全字段可选版本BaseModel
。考虑完所有这些之后这个特定的答案 https://stackoverflow.com/a/72365032 by 齐尔·奥尔帕 https://stackoverflow.com/users/10416012/ziur-olpa在我看来,这是最好的,提供了一个函数,该函数采用带有可选和强制字段的现有模型,并返回一个新模型所有字段可选: https://stackoverflow.com/a/72365032 https://stackoverflow.com/a/72365032
这种方法的优点在于,您可以隐藏库中的(实际上非常紧凑)小函数,并将其用作依赖项,以便它内联显示在路径操作函数中,并且没有其他代码或样板。
但是前面的答案中提供的实现并没有采取处理子对象的步骤BaseModel
正在修补。
因此,这个问题要求改进所有字段可选函数的实现,该函数还处理子对象,以及带有更新的深层复制。
我有一个简单的示例来演示此用例,虽然其目的是为了演示目的而简单,但也包含许多字段以更接近地反映我们看到的现实世界示例。希望这个示例为实现提供一个测试场景,从而节省工作量:
import logging
from datetime import datetime, date
from collections import defaultdict
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException, status, Depends
from fastapi.encoders import jsonable_encoder
app = FastAPI(title="PATCH demo")
logging.basicConfig(level=logging.DEBUG)
class Collection:
collection = defaultdict(dict)
def __init__(self, this, that):
logging.debug("-".join((this, that)))
self.this = this
self.that = that
def get_document(self):
document = self.collection[self.this].get(self.that)
if not document:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Not Found",
)
logging.debug(document)
return document
def save_document(self, document):
logging.debug(document)
self.collection[self.this][self.that] = document
return document
class SubOne(BaseModel):
original: date
verified: str = ""
source: str = ""
incurred: str = ""
reason: str = ""
attachments: list[str] = []
class SubTwo(BaseModel):
this: str
that: str
amount: float
plan_code: str = ""
plan_name: str = ""
plan_type: str = ""
meta_a: str = ""
meta_b: str = ""
meta_c: str = ""
class Document(BaseModel):
this: str
that: str
created: datetime
updated: datetime
sub_one: SubOne
sub_two: SubTwo
the_code: str = ""
the_status: str = ""
the_type: str = ""
phase: str = ""
process: str = ""
option: str = ""
@app.get("/endpoint/{this}/{that}", response_model=Document)
async def get_submission(this: str, that: str) -> Document:
collection = Collection(this=this, that=that)
return collection.get_document()
@app.put("/endpoint/{this}/{that}", response_model=Document)
async def put_submission(this: str, that: str, document: Document) -> Document:
collection = Collection(this=this, that=that)
return collection.save_document(jsonable_encoder(document))
@app.patch("/endpoint/{this}/{that}", response_model=Document)
async def patch_submission(
document: Document,
# document: optional(Document), # <<< IMPLEMENT optional <<<
this: str,
that: str,
) -> Document:
collection = Collection(this=this, that=that)
existing = collection.get_document()
existing = Document(**existing)
update = document.dict(exclude_unset=True)
updated = existing.copy(update=update, deep=True) # <<< FIX THIS <<<
updated = jsonable_encoder(updated)
collection.save_document(updated)
return updated
此示例是一个正在运行的 FastAPI 应用程序,遵循教程,可以使用以下命令运行uvicorn example:app --reload
。但它不起作用,因为没有全可选字段模型,而 Pydantic 的深度复制实际上带有更新覆盖子模型而不是updating them.
为了测试它,可以使用以下 Bash 脚本来运行curl
要求。我再次提供这个只是为了希望能够更容易地开始解决这个问题。
只需在每次运行时注释掉其他命令,以便使用您想要的命令。
为了演示示例应用程序的初始状态,您将运行GET
(预计 404),PUT
(存储的文档),GET
(预计返回 200 个和相同的文档),PATCH
(预计 200),GET
(预计返回 200 和更新的文档)。
host='http://127.0.0.1:8000'
path="/endpoint/A123/B456"
method='PUT'
data='
{
"this":"A123",
"that":"B456",
"created":"2022-12-01T01:02:03.456",
"updated":"2023-01-01T01:02:03.456",
"sub_one":{"original":"2022-12-12","verified":"Y"},
"sub_two":{"this":"A123","that":"B456","amount":0.88,"plan_code":"HELLO"},
"the_code":"BYE"}
'
# method='PATCH'
# data='{"this":"A123","that":"B456","created":"2022-12-01T01:02:03.456","updated":"2023-01-02T03:04:05.678","sub_one":{"original":"2022-12-12","verified":"N"},"sub_two":{"this":"A123","that":"B456","amount":123.456}}'
method='GET'
data=''
if [[ -n data ]]; then data=" --data '$data'"; fi
curl="curl -K curlrc -X $method '$host$path' $data"
echo $curl >&2
eval $curl
This curlrc
需要位于同一位置以确保内容类型标头正确:
--cookie "_cookies"
--cookie-jar "_cookies"
--header "Content-Type: application/json"
--header "Accept: application/json"
--header "Accept-Encoding: compress, gzip"
--header "Cache-Control: no-cache"
所以我正在寻找的是实施optional
代码中已注释掉,并修复了existing.copy
与update
参数,这将使该示例能够与PATCH
省略其他强制字段的调用。
实现不必完全符合注释掉的行,我只是根据齐尔·奥尔帕的 https://stackoverflow.com/users/10416012/ziur-olpa以前的answer https://stackoverflow.com/a/72365032.