fastapi form data with pydantic model

浪子不回头ぞ 提交于 2020-05-31 20:45:08

问题


I am trying to submit data from html forms and on the validate it with pydantic model.
Using this code

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

How ever I get the error with http status 422 Unprocessable Entity

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

Equivalent curl command (generated by firfox) is

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

Here the request body contains no=1&nm=abcd

What am i doing wrong?


回答1:


I found a solution which can help us to use FastAPI forms as pydantic as well :)
My code:

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

It's showed in the swagger as usual form
I think, it can be written more generic, maybe I will return and edit the answer.

[UPDATED]

I've written it more generic as a decorator

import inspect
from typing import Type
from fastapi import Form, UploadFile, File
from pydantic import BaseModel


def as_form(cls: Type[BaseModel]):
    defaults = cls.__field_defaults__
    new_parameters = []
    for key, value in cls.__annotations__.items():
        default_value = defaults.get(key)
        if value == UploadFile:
            new_parameters.append(
                inspect.Parameter(
                    key,
                    inspect._POSITIONAL_ONLY,
                    default=File(default_value),
                    annotation=value,
                )
            )
            continue
        if default_value is not None:
            new_parameters.append(
                inspect.Parameter(
                    key,
                    inspect._POSITIONAL_ONLY,
                    default=Form(default_value),
                    annotation=value,
                )
            )
        else:
            new_parameters.append(
                inspect.Parameter(
                    key, inspect._POSITIONAL_ONLY, default=Form(...), annotation=value
                )
            )
    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
    cls.as_form = as_form_func
    return cls

And usage looks like

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



回答2:


you can use data-form like below:

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



回答3:


If you're only looking at abstracting the form data into a class you can do it with a plain class

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

And it can also be turned into a 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


来源:https://stackoverflow.com/questions/60127234/fastapi-form-data-with-pydantic-model

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!