带有pydantic模型的fastapi表单数据

sha*_*uga 9 python pydantic fastapi

我正在尝试从 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
Run Code Online (Sandbox Code Playgroud)

我如何收到 http 状态422不可处理实体的错误

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

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

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

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

我究竟做错了什么?

Nik*_*dov 21

我找到了一个解决方案,它可以帮助我们将 FastAPI 表单也用作 pydantic :)
我的代码:

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)):
        ...
Run Code Online (Sandbox Code Playgroud)


我认为它像往常一样以招摇的形式显示,它可以写得更通用,也许我会返回并编辑答案。

[更新]

我把它写成更通用的装饰器

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
Run Code Online (Sandbox Code Playgroud)

用法看起来像

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
Run Code Online (Sandbox Code Playgroud)

  • 此问题有此方法的更清晰版本https://github.com/tiangolo/fastapi/issues/2387 (2认同)

iud*_*een 15

您可以使用数据类更简单地做到这一点

from dataclasses import dataclass
from fastapi import FastAPI, Form, Depends
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>'''


@dataclass
class SimpleModel:
    no: int = Form(...)
    nm: str = Form(...)


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

Run Code Online (Sandbox Code Playgroud)

  • 数据验证速度,真的吗?您真的关心将 HTTP 请求减少几毫秒吗?我只熟悉极少数重要的用例。 (5认同)

小智 9

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

from fastapi.testclient import TestClient
from fastapi import FastAPI, Depends, Form
from pydantic import BaseModel


app = FastAPI()


def form_body(cls):
    cls.__signature__ = cls.__signature__.replace(
        parameters=[
            arg.replace(default=Form(...))
            for arg in cls.__signature__.parameters.values()
        ]
    )
    return cls


@form_body
class Item(BaseModel):
    name: str
    another: str


@app.post('/test', response_model=Item)
def endpoint(item: Item = Depends(Item)):
    return item


tc = TestClient(app)


r = tc.post('/test', data={'name': 'name', 'another': 'another'})

assert r.status_code == 200
assert r.json() == {'name': 'name', 'another': 'another'}
Run Code Online (Sandbox Code Playgroud)


小智 5

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

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