FastAPI 的部分更新
Partial update in FastAPI
我想在支持部分更新的 FastAPI 中实现放置或补丁请求。 The official documentation 真的很混乱,我不知道该怎么做。 (我不知道 items
在文档中,因为我的数据将与请求的主体一起传递,而不是硬编码的字典)。
class QuestionSchema(BaseModel):
title: str = Field(..., min_length=3, max_length=50)
answer_true: str = Field(..., min_length=3, max_length=50)
answer_false: List[str] = Field(..., min_length=3, max_length=50)
category_id: int
class QuestionDB(QuestionSchema):
id: int
async def put(id: int, payload: QuestionSchema):
query = (
questions
.update()
.where(id == questions.c.id)
.values(**payload)
.returning(questions.c.id)
)
return await database.execute(query=query)
@router.put("/{id}/", response_model=QuestionDB)
async def update_question(payload: QuestionSchema, id: int = Path(..., gt=0),):
question = await crud.get(id)
if not question:
raise HTTPException(status_code=404, detail="question not found")
## what should be the stored_item_data, as documentation?
stored_item_model = QuestionSchema(**stored_item_data)
update_data = payload.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
response_object = {
"id": question_id,
"title": payload.title,
"answer_true": payload.answer_true,
"answer_false": payload.answer_false,
"category_id": payload.category_id,
}
return response_object
如何完成我的代码以在此处获得成功的部分更新?
我在 FastAPI 的 Github 问题上得到了这个答案:
您可以将基础字段设为可选 class 并创建一个扩展 QuestionSchema 的新 QuestionCreate 模型。例如:
from typing import Optional
class Question(BaseModel):
title: Optional[str] = None # title is optional on the base schema
...
class QuestionCreate(Question):
title: str # Now title is required
cookiecutter 模板 here 也提供了一些很好的见解。
在这里发布这篇文章是为了寻找一种直观的解决方案来创建他们的 pydantic 模型的可选版本而无需代码重复的 googlers。
假设我们有一个 User
模型,我们希望允许 PATCH 请求更新用户。但是我们需要创建一个模式来告诉 FastApi 在内容主体中期望什么,特别是所有字段都是可选的(因为这是 PATCH 请求的本质)。我们可以在不重新定义所有字段的情况下这样做
from pydantic import BaseModel
from typing import Optional
# Creating our Base User Model
class UserBase(BaseModel):
username: str
email: str
# And a Model that will be used to create an User
class UserCreate(UserBase):
password: str
代码重复 ❌
class UserOptional(UserCreate):
username: Optional[str]
email: Optional[str]
password: Optional[str]
一个班轮 ✅
# Now we can make a UserOptional class that will tell FastApi that all the fields are optional.
# Doing it this way cuts down on the duplication of fields
class UserOptional(UserCreate):
__annotations__ = {k: Optional[v] for k, v in UserCreate.__annotations__.items()}
注意:即使模型中的某个字段已经是可选的,由于可选的性质 typing.Union[type passed to Optional, None]
在后台,它也不会有所作为。
即typing.Union[str, None] == typing.Optional[str]
如果你要多次使用它,你甚至可以把它变成一个函数:
def convert_to_optional(schema):
return {k: Optional[v] for k, v in schema.__annotations__.items()}
class UserOptional(UserCreate):
__annotations__ = convert_to_optional(UserCreate)
根据@cdraper的回答,做了一个局部模型工厂:
from typing import Mapping, Any, List, Type
from pydantic import BaseModel
def model_annotations_with_parents(model: BaseModel) -> Mapping[str, Any]:
parent_models: List[Type] = [
parent_model for parent_model in model.__bases__
if (
issubclass(parent_model, BaseModel)
and hasattr(parent_model, '__annotations__')
)
]
annotations: Mapping[str, Any] = {}
for parent_model in reversed(parent_models):
annotations.update(model_annotations_with_parents(parent_model))
annotations.update(model.__annotations__)
return annotations
def partial_model_factory(model: BaseModel, prefix: str = "Partial", name: str = None) -> BaseModel:
if not name:
name = f"{prefix}{model.__name__}"
return type(
name, (model,),
dict(
__module__=model.__module__,
__annotations__={
k: Optional[v]
for k, v in model_annotations_with_parents(model).items()
}
)
)
def partial_model(cls: BaseModel) -> BaseModel:
return partial_model_factory(cls, name=cls.__name__)
可与函数partial_model_factory
一起使用:
PartialQuestionSchema = partial_model_factory(QuestionSchema)
或者使用装饰器 partial_model
:
@partial_model
class PartialQuestionSchema(QuestionSchema):
pass
我想在支持部分更新的 FastAPI 中实现放置或补丁请求。 The official documentation 真的很混乱,我不知道该怎么做。 (我不知道 items
在文档中,因为我的数据将与请求的主体一起传递,而不是硬编码的字典)。
class QuestionSchema(BaseModel):
title: str = Field(..., min_length=3, max_length=50)
answer_true: str = Field(..., min_length=3, max_length=50)
answer_false: List[str] = Field(..., min_length=3, max_length=50)
category_id: int
class QuestionDB(QuestionSchema):
id: int
async def put(id: int, payload: QuestionSchema):
query = (
questions
.update()
.where(id == questions.c.id)
.values(**payload)
.returning(questions.c.id)
)
return await database.execute(query=query)
@router.put("/{id}/", response_model=QuestionDB)
async def update_question(payload: QuestionSchema, id: int = Path(..., gt=0),):
question = await crud.get(id)
if not question:
raise HTTPException(status_code=404, detail="question not found")
## what should be the stored_item_data, as documentation?
stored_item_model = QuestionSchema(**stored_item_data)
update_data = payload.dict(exclude_unset=True)
updated_item = stored_item_model.copy(update=update_data)
response_object = {
"id": question_id,
"title": payload.title,
"answer_true": payload.answer_true,
"answer_false": payload.answer_false,
"category_id": payload.category_id,
}
return response_object
如何完成我的代码以在此处获得成功的部分更新?
我在 FastAPI 的 Github 问题上得到了这个答案:
您可以将基础字段设为可选 class 并创建一个扩展 QuestionSchema 的新 QuestionCreate 模型。例如:
from typing import Optional
class Question(BaseModel):
title: Optional[str] = None # title is optional on the base schema
...
class QuestionCreate(Question):
title: str # Now title is required
cookiecutter 模板 here 也提供了一些很好的见解。
在这里发布这篇文章是为了寻找一种直观的解决方案来创建他们的 pydantic 模型的可选版本而无需代码重复的 googlers。
假设我们有一个 User
模型,我们希望允许 PATCH 请求更新用户。但是我们需要创建一个模式来告诉 FastApi 在内容主体中期望什么,特别是所有字段都是可选的(因为这是 PATCH 请求的本质)。我们可以在不重新定义所有字段的情况下这样做
from pydantic import BaseModel
from typing import Optional
# Creating our Base User Model
class UserBase(BaseModel):
username: str
email: str
# And a Model that will be used to create an User
class UserCreate(UserBase):
password: str
代码重复 ❌
class UserOptional(UserCreate):
username: Optional[str]
email: Optional[str]
password: Optional[str]
一个班轮 ✅
# Now we can make a UserOptional class that will tell FastApi that all the fields are optional.
# Doing it this way cuts down on the duplication of fields
class UserOptional(UserCreate):
__annotations__ = {k: Optional[v] for k, v in UserCreate.__annotations__.items()}
注意:即使模型中的某个字段已经是可选的,由于可选的性质 typing.Union[type passed to Optional, None]
在后台,它也不会有所作为。
即typing.Union[str, None] == typing.Optional[str]
如果你要多次使用它,你甚至可以把它变成一个函数:
def convert_to_optional(schema):
return {k: Optional[v] for k, v in schema.__annotations__.items()}
class UserOptional(UserCreate):
__annotations__ = convert_to_optional(UserCreate)
根据@cdraper的回答,做了一个局部模型工厂:
from typing import Mapping, Any, List, Type
from pydantic import BaseModel
def model_annotations_with_parents(model: BaseModel) -> Mapping[str, Any]:
parent_models: List[Type] = [
parent_model for parent_model in model.__bases__
if (
issubclass(parent_model, BaseModel)
and hasattr(parent_model, '__annotations__')
)
]
annotations: Mapping[str, Any] = {}
for parent_model in reversed(parent_models):
annotations.update(model_annotations_with_parents(parent_model))
annotations.update(model.__annotations__)
return annotations
def partial_model_factory(model: BaseModel, prefix: str = "Partial", name: str = None) -> BaseModel:
if not name:
name = f"{prefix}{model.__name__}"
return type(
name, (model,),
dict(
__module__=model.__module__,
__annotations__={
k: Optional[v]
for k, v in model_annotations_with_parents(model).items()
}
)
)
def partial_model(cls: BaseModel) -> BaseModel:
return partial_model_factory(cls, name=cls.__name__)
可与函数partial_model_factory
一起使用:
PartialQuestionSchema = partial_model_factory(QuestionSchema)
或者使用装饰器 partial_model
:
@partial_model
class PartialQuestionSchema(QuestionSchema):
pass