FastAPI之Form表单校验

854 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

今天来说一个最近工作中遇到的问题,最近项目在使用FastAPI来做为微服务api后台的框架。这里先对FastAPI做个简单介绍:


FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.6+ 并基于标准的 Python 类型提示。拥有快速高效编码更少bug智能简单简短健壮标准化等优点


但是由于目前国内使用的人不多,所以很多资料在国内的网站上都很少有记录和展现,在工作中之前就遇到一个问题,fastapi的请求体的嵌套模型无法适用于form表单数据,也就是说无法很容易的对form表单的传参进行相对简单的一个校验,这样就导致一个很严重的问题,无法使用fastapi较为优秀的自动化校验功能,这会给代码代码非常多的不稳定因素。因此本文记录了一下有关于fastapi的对form表单进行校验的一种可用的方法。

在了解这种方法之前,我们要对fastapi的传参又个简单大致的了解

众所周知,对于url传参以及body传参都可以使用如下两种方法

@app.get("/items/{item_id}")
def itemq(item_id: int):
    results = {"item_id": item_id}
    return results
class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    tags: list = []

@app.put("/items")
def itemq(item: Item):
    results = {"item": item}
    return results

当然,校验规则和方法还有很多种的,具体可以在官方文档找到,都有非常详细的说明介绍:fastapi.tiangolo.com/zh/ 但是,如果请求参数是form表单的话会报一个异常

1 validation error for Request\nbody\n  value is not a valid dict (type=type_error.dict)

解决方法参考了stackoverflow.com中的一个方法,同时也是FastAPI官方github 上Issues中提到的一个问题:multipart/form-data:无法解析请求表单中的复杂类型 方法如下,通过类装饰其将form表单的数据解析出来,再通过注入依赖(Depends)方式将请求参数校验模型注入进去实现对form表单的校验。

    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 
    
    
    
#自定义替换任意一个参数
    class Item2(BaseModel): 
        name: str 
        another: str 
        
        @classmethod
        def as_form(cls,
                    name:str=Form(...),
                    file=file:Optional[UploadFile]=File(None),)
            return cls(name=name,file=file)
    
    # 少写代码直接全局替换为form
    @form_body 
    class Item(BaseModel): 
        name: str 
        another: str 
    
    
    @app.post('/test', response_model=Item)
    def endpoint(item: Item = Depends(Item)):
        return item
    
    
    @app.post('/test2', response_model=Item2)
    def endpoint(item: Item2 = Depends(Item2.as_form)):
        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'}