在FastAPI中如何使用pydantic的BaseModel验证Form请求

在使用FastAPI的时候,如果我们想使用Form表单提交数据这个时候使用示例如下:

1
2
3
4
5
6
7
8
from fastapi import FastAPI, Form

app = FastAPI()


@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}

但是这种方式当参数过多,或者更高级的验证的时候就会显得复杂,能不能使用pydanticBaseModel验证Form请求呢?

多方查找终于在 stackoverflow 找到了这个问答:fastapi form data with pydantic model

这个文档再FastAPI的官方github上引出了一个issue:multipart/form-data: Unable to parse complex types in a request form

从issue中我选择了一个最优的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def as_form(cls: Type[BaseModel]) -> Type[BaseModel]:
"""
Adds an `as_form` class method to decorated models. The `as_form` class
method can be used with `FastAPI` endpoints.

Args:
cls: The model class to decorate.

Returns:
The decorated class.

"""

def make_form_parameter(field: ModelField) -> Any:
"""
Converts a field from a `Pydantic` model to the appropriate `FastAPI`
parameter type.

Args:
field: The field to convert.

Returns:
Either the result of `Form`, if the field is not a sub-model, or
the result of `Depends` if it is.

"""
# 支持字段的类型还是 BaseModel
if issubclass(field.type_, BaseModel):
# This is a sub-model.
assert hasattr(field.type_, "as_form"), (
f"Sub-model class for {field.name} field must be decorated with"
f" `as_form` too."
)
return Depends(field.type_.as_form)
else:
return Form(field.default) if not field.required else Form(...)

new_params = [
inspect.Parameter(
field.alias,
inspect.Parameter.POSITIONAL_ONLY,
default=make_form_parameter(field),
)
for field in cls.__fields__.values()
]

async def _as_form(**data):
return cls(**data)

sig = inspect.signature(_as_form)
sig = sig.replace(parameters=new_params)
_as_form.__signature__ = sig
setattr(cls, "as_form", _as_form)
return cls

上面这个装饰器还支持参数类型递归

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
@as_form
class Nested(BaseModel):
foo: int
bar: str

@as_form
class Outer(BaseModel):
inner: Inner
baz: float

@app.post("/test")
async def test_form(form: Outer = Depends(Outer.as_form)):
return {"foo": form.inner.foo, "bar": form.inner.bar, "baz": form.baz}

这样我们提交的Form表单也能使用pydanticBaseModel验证了。

知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!