问题
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