2

我想在支持部分更新的 FastAPI 中实现 put 或 patch 请求。官方文档真的很混乱,我无法弄清楚如何提出请求。(我不知道这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

如何在此处完成我的代码以获得成功的部分更新?

4

3 回答 3

3

我在 FastAPI 的 Github 问题上得到了这个答案:

您可以在基类上将字段设为可选,并创建一个扩展 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 模板也提供了一些很好的见解。

于 2020-05-15T11:32:39.630 回答
1

在这里为那些正在寻找一种直观的解决方案来创建他们的 pydantic 模型的可选版本而不重复代码的谷歌用户发布此内容。

假设我们有一个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

代码重复❌</h3>
class UserOptional(UserCreate):
    username: Optional[str]
    email: Optional[str]
    password: Optional[str]

一个班轮✅</h3>
# 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]在后台,它也不会产生任何影响。

IEtyping.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)

于 2022-02-16T02:44:51.717 回答
0

根据@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
于 2022-02-28T17:00:12.870 回答