如何在 FastAPI 中使用 Pydantic 模型和表单数据?

Posted

技术标签:

【中文标题】如何在 FastAPI 中使用 Pydantic 模型和表单数据?【英文标题】:How to use a Pydantic model with Form data in FastAPI? 【发布时间】:2020-05-24 09:41:41 【问题描述】:

我正在尝试从 html 表单提交数据并使用 Pydantic 模型对其进行验证。

使用此代码

from fastapi import FastAPI, Form
from pydantic import BaseModel
from starlette.responses import HTMLResponse


app = FastAPI()

@app.get("/form", response_class=HTMLResponse)
def form_get():
    return '''<form method="post"> 
    <input type="text" name="no" value="1"/> 
    <input type="text" name="nm" value="abcd"/> 
    <input type="submit"/> 
    </form>'''


class SimpleModel(BaseModel):
    no: int
    nm: str = ""

@app.post("/form", response_model=SimpleModel)
def form_post(form_data: SimpleModel = Form(...)):
    return form_data

但是,我收到 HTTP 错误:“422 Unprocessable Entity”


    "detail": [
        
            "loc": [
                "body",
                "form_data"
            ],
            "msg": "field required",
            "type": "value_error.missing"
        
    ]

等效的 curl 命令(由 Firefox 生成)是

curl 'http://localhost:8001/form' -H 'Content-Type: application/x-www-form-urlencoded' --data 'no=1&nm=abcd'

这里的请求正文包含no=1&amp;nm=abcd

我做错了什么?

【问题讨论】:

看来正文是空的,或者至少缺少form_data。但是如果没有看到您提交的内容,就无法提供更多帮助。 在上面的代码中,GET 请求给出了一个 HTML 表单,我点击了提交。我给出的所有值都会出错。 找出问题的第一步是检查 POST 请求并查看提交的内容。 请求正文包含no=1&amp;nm=abcd 【参考方案1】:

我找到了一个解决方案,可以帮助我们将 Pydantic 与 FastAPI 表单一起使用 :)

我的代码:

class AnyForm(BaseModel):
    any_param: str
    any_other_param: int = 1

    @classmethod
    def as_form(
        cls,
        any_param: str = Form(...),
        any_other_param: int = Form(1)
    ) -> AnyForm:
        return cls(any_param=any_param, any_other_param=any_other_param)

@router.post('')
async def any_view(form_data: AnyForm = Depends(AnyForm.as_form)):
        ...

它在 Swagger 中以通常的形式显示。

作为装饰器可以更通用:

import inspect
from typing import Type

from fastapi import Form
from pydantic import BaseModel
from pydantic.fields import ModelField

def as_form(cls: Type[BaseModel]):
    new_parameters = []

    for field_name, model_field in cls.__fields__.items():
        model_field: ModelField  # type: ignore

        if not model_field.required:
            new_parameters.append(
                inspect.Parameter(
                    model_field.alias,
                    inspect.Parameter.POSITIONAL_ONLY,
                    default=Form(model_field.default),
                    annotation=model_field.outer_type_,
                )
            )
        else:
            new_parameters.append(
                inspect.Parameter(
                    model_field.alias,
                    inspect.Parameter.POSITIONAL_ONLY,
                    default=Form(...),
                    annotation=model_field.outer_type_,
                )
            )

    async def as_form_func(**data):
        return cls(**data)

    sig = inspect.signature(as_form_func)
    sig = sig.replace(parameters=new_parameters)
    as_form_func.__signature__ = sig  # type: ignore
    setattr(cls, 'as_form', as_form_func)
    return cls

而且用法看起来像

class Test1(BaseModel):
    a: str
    b: int


@as_form
class Test(BaseModel):
    param: str
    test: List[Test1]
    test1: Test1
    b: int = 1
    a: str = '2342'


@router.post('/me', response_model=Test)
async def me(request: Request, form: Test = Depends(Test.as_form)):
    return form

【讨论】:

不确定这有什么帮助。你能举一个简短的例子吗? 立即查看 这个问题有一个更简洁的版本github.com/tiangolo/fastapi/issues/2387 如何在 swagger ui 中使用这个来命名身体模型?这是我想使用 Pydantic 类的唯一原因。【参考方案2】:

我实现了在 Mause solution 找到的解决方案,它似乎有效

从 fastapi.testclient 导入 TestClient 从 fastapi 导入 FastAPI,依赖,表单 从 pydantic 导入 BaseModel 应用程序 = FastAPI() def form_body(cls): cls.__signature__ = cls.__signature__.replace( 参数=[ arg.replace(default=Form(...)) 对于 cls.__signature__.parameters.values() 中的 arg ] ) 返回 cls @form_body 类项目(BaseModel): 名称:str 另一个:str @app.post('/test', response_model=Item) def端点(项目:项目=依赖(项目)): 归还物品 tc = TestClient(app) r = tc.post('/test', data='name': 'name', 'another': 'another') 断言 r.status_code == 200 断言 r.json() == 'name': 'name', 'another': 'another'

【讨论】:

【参考方案3】:

如果您只是想将表单数据抽象到一个类中,您可以使用普通类来完成

from fastapi import Form, Depends

class AnyForm:
    def __init__(self, any_param: str = Form(...), any_other_param: int = Form(1)):
        self.any_param = any_param
        self.any_other_param = any_other_param

    def __str__(self):
        return "AnyForm " + str(self.__dict__)

@app.post('/me')
async def me(form: AnyForm = Depends()):
    print(form)
    return form

它也可以变成Pydantic Model

from uuid import UUID, uuid4
from fastapi import Form, Depends
from pydantic import BaseModel

class AnyForm(BaseModel):
    id: UUID
    any_param: str
    any_other_param: int

    def __init__(self, any_param: str = Form(...), any_other_param: int = Form(1)):
        id = uuid4()
        super().__init__(id, any_param, any_other_param)

@app.post('/me')
async def me(form: AnyForm = Depends()):
    print(form)
    return form

【讨论】:

【参考方案4】:

您可以使用如下数据形式:

@app.post("/form", response_model=SimpleModel)
def form_post(no: int = Form(...),nm: str = Form(...)):
    return SimpleModel(no=no,nm=nm)

【讨论】:

感谢您的回答,但这无济于事。我要求具体用法。我试图避免任何额外的代码增加复杂性。我还计划将它与从表单提交的其他简单变量/文件混合。类似于使用PathBody 可以完成的操作

以上是关于如何在 FastAPI 中使用 Pydantic 模型和表单数据?的主要内容,如果未能解决你的问题,请参考以下文章

您将如何使用带有 FastAPI 的 asyncpg 将选择查询的返回值映射到 pydantic 模型以进行输出和验证?

单独文件中的 FastAPI / Pydantic 循环引用

如何通过 pydantic 模式重命名来自数据库的响应 - FastAPI

FastAPI Web框架 [Pydantic]

FastAPI Web框架 [Pydantic]

FastAPI Web框架 [Pydantic]