如何在 FastAPI 中使用带有表单数据的 Pydantic 模型?
How to use a Pydantic model with Form data in 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
但是,我收到 HTTP 错误:“422
无法处理的实体”
{
"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&nm=abcd
.
我做错了什么?
您可以使用如下数据形式:
@app.post("/form", response_model=SimpleModel)
def form_post(no: int = Form(...),nm: str = Form(...)):
return SimpleModel(no=no,nm=nm)
我找到了一个可以帮助我们将 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
new_parameters.append(
inspect.Parameter(
model_field.alias,
inspect.Parameter.POSITIONAL_ONLY,
default=Form(...) if not model_field.required else Form(model_field.default),
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
用法看起来像
@as_form
class Test(BaseModel):
param: str
a: int = 1
b: str = '2342'
c: bool = False
d: Optional[float] = None
@router.post('/me', response_model=Test)
async def me(request: Request, form: Test = Depends(Test.as_form)):
return form
如果您只想将表单数据抽象为 class,您可以使用普通的 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
而且还可以变成Pydantic模型
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
我实施了此处找到的解决方案 Mause solution,它似乎有效
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'}
这样创建 class:
from fastapi import Form
class SomeForm:
def __init__(
self,
username: str = Form(...),
password: str = Form(...),
authentication_code: str = Form(...)
):
self.username = username
self.password = password
self.authentication_code = authentication_code
@app.post("/login", tags=['Auth & Users'])
async def auth(
user: SomeForm = Depends()
):
# return something / set cookie
Result
如果你想制作一个 http 请求表单 javascript 你必须使用 FormData 来构建请求:
const fd = new FormData()
fd.append('username', username)
fd.append('password', password)
axios.post(`/login`, fd)
我正在尝试从 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
无法处理的实体”
{
"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&nm=abcd
.
我做错了什么?
您可以使用如下数据形式:
@app.post("/form", response_model=SimpleModel)
def form_post(no: int = Form(...),nm: str = Form(...)):
return SimpleModel(no=no,nm=nm)
我找到了一个可以帮助我们将 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
new_parameters.append(
inspect.Parameter(
model_field.alias,
inspect.Parameter.POSITIONAL_ONLY,
default=Form(...) if not model_field.required else Form(model_field.default),
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
用法看起来像
@as_form
class Test(BaseModel):
param: str
a: int = 1
b: str = '2342'
c: bool = False
d: Optional[float] = None
@router.post('/me', response_model=Test)
async def me(request: Request, form: Test = Depends(Test.as_form)):
return form
如果您只想将表单数据抽象为 class,您可以使用普通的 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
而且还可以变成Pydantic模型
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
我实施了此处找到的解决方案 Mause solution,它似乎有效
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'}
这样创建 class:
from fastapi import Form
class SomeForm:
def __init__(
self,
username: str = Form(...),
password: str = Form(...),
authentication_code: str = Form(...)
):
self.username = username
self.password = password
self.authentication_code = authentication_code
@app.post("/login", tags=['Auth & Users'])
async def auth(
user: SomeForm = Depends()
):
# return something / set cookie
Result
如果你想制作一个 http 请求表单 javascript 你必须使用 FormData 来构建请求:
const fd = new FormData()
fd.append('username', username)
fd.append('password', password)
axios.post(`/login`, fd)