如何在 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&nm=abcd
。
我做错了什么?
【问题讨论】:
看来正文是空的,或者至少缺少form_data
。但是如果没有看到您提交的内容,就无法提供更多帮助。
在上面的代码中,GET 请求给出了一个 HTML 表单,我点击了提交。我给出的所有值都会出错。
找出问题的第一步是检查 POST 请求并查看提交的内容。
请求正文包含no=1&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)
【讨论】:
感谢您的回答,但这无济于事。我要求具体用法。我试图避免任何额外的代码增加复杂性。我还计划将它与从表单提交的其他简单变量/文件混合。类似于使用Path
或Body
可以完成的操作以上是关于如何在 FastAPI 中使用 Pydantic 模型和表单数据?的主要内容,如果未能解决你的问题,请参考以下文章
您将如何使用带有 FastAPI 的 asyncpg 将选择查询的返回值映射到 pydantic 模型以进行输出和验证?
单独文件中的 FastAPI / Pydantic 循环引用