python系列:如何在FastAPI中使用Pydantic的BaseModel上传文件和字典列表?




如何在FastAPI中使用Pydantic的BaseModel上传文件和字典列表?

问题:

我有以下代码示例:

from fastapi import File, UploadFile, Request, FastAPI, Depends
from typing import List
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, Field
from typing import Optional

app = FastAPI()

class BaseBox(BaseModel):
    l: float=Field(...)
    t: float=Field(...)
    r: float=Field(...)
    b: float=Field(...)

class BaseInput(BaseModel):
    boxes: List[BaseBox] = Field(...)
    words: List[str] = Field(...)
    width: Optional[float] = Field(...)
    height: Optional[float] = Field(...)

@app.post("/submit")
def submit(
    base_input: BaseInput = Depends(),
    file: UploadFile = File(...),  # Add this line to accept a file
):

    return {
        "JSON Payload": base_input,
        "Filename": file.filename,
    }

@app.get("/")
def main(request: Request):
    return {"status":"alive"}

但有些我怎么做不到。我使用交互式API文档,但总是出现错误。你认为我必须发送两个文件吗?我也试过

curl -X 'POST' \
  'http://localhost:8007/submit?width=10&height=10' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F '[email protected];type=image/png' \
  -F 'boxes={
  "l": 0,
  "t": 0,
  "r": 0,
  "b": 0
}' \
  -F 'words=test,test2,tes3,test'

但我总是得到错误

"POST /submit?width=10&height=10 HTTP/1.1" 422 Unprocessable Entity。

回答:

正如您提供的代码所示,您已经了解了这个答案,这就是您最终应该找到的解决方案。

但是,让我解释一下您的示例中的代码有什么问题。您同时提交了文件和查询数据,或者,至少,这似乎是您一直在努力实现的。在端点或Pydantic模型中定义查询参数,例如,定义为strint,并在端点中的参数上使用Depends()来指示在BaseModel中定义的文件应作为查询参数,在这两种情况下,都应该可以正常工作。但是,当您直接在端点或在BaseModel中将参数定义为Liste.g.、List[int]List[str]时,您应该使用Query显式定义它,如这里和这里所解释和演示的。

虽然Pydantic模型过去不允许使用Query字段,并且必须在单独的依赖类中实现查询parameter-parsing,如本答案和本答案所示,但这一点最近发生了变化,因此,可以使用BaseModel类Query()封装在Field()中,如本回答所示。

工作示例1
from fastapi import Query
from pydantic import BaseModel, Field
from typing import Optional

class Base(BaseModel):
    width: Optional[float] = Field (...)
    height: Optional[float] = Field (...)
    words: List[str] = Field (Query(...))  #  wrap the `Query()` in a `Field()` for `List` params
    

@app.get('/')
async def main(base: Base = Depends()):
    pass

在您的示例中,您似乎还有一个List查询参数,该参数需要一个dictionary/JSON对象列表作为值。但是,使用查询参数无法实现这一点。如果您试图在上面的工作示例中定义这样的参数(e.g.,boxes: List[BaseBox] = Field (Query(...))),则在尝试运行FastAPI应用程序时会遇到以下错误:

断言错误:参数:boxes只能是请求体,使用Body()

如果您将参数定义为boxes: List[BaseBox] = Field (...),以及在端点中定义的file: UploadFile = File(...),就像您在代码中已经做的那样,即使应用程序会像往常一样开始运行,但当尝试向该端点提交请求时(例如,通过位于/docs的Swagger UI autodocs),您会收到一个422 Unprocessable Entity错误,并显示一条具有类似含义的消息,说Input should be a valid dictionary or object to extract fields from

这是因为,在第一种情况下,您不能有一个期望字典数据的查询参数(除非您对任意查询数据遵循此处和此处描述的方法,您需要自己解析这些数据,我不建议这样做),而在第二种情况中,由于端点中定义的file: UploadFile = File(...),请求体被编码为multipart/form-data发送;但是,HTTP协议不支持同时发送FormJSON数据(请再次参阅此答案)。

但是,如果您从端点中删除了UploadFile参数,那么请求应该会成功通过,因为请求体将被内插为application/json(提交请求时,请查看Swagger UI中的Content-Type请求头)。

工作示例2
from fastapi import Query
from pydantic import BaseModel, Field
from typing import Optional

class BaseBox(BaseModel):
    l: float = Field(...)
    t: float = Field(...)
    
class Base(BaseModel):
    width: Optional[float] = Field (...)
    height: Optional[float] = Field (...)
    words: List[str] = Field (Query(...))  #  wrap the `Query()` in a `Field()` for `List` params
    boxes: List[BaseBox] = Field (...)


@app.get('/')
async def main(base: Base = Depends()):
    pass

发布文件和JSON正文(包括字典的List)

如果您仍然需要在FastAPI POST请求中同时添加File(s)JSON主体,我强烈建议您看看这个答案的方法3和4。下面提供的示例基于链接答案中的这两种方法,并演示了如何将FilesJSON数据一起发布,JSON数据还包括字典列表,就像您的示例一样。请查看链接的答案,了解有关如何测试这些方法的更多详细信息和PythonJavaScript中的示例。在您的情况下,您需要将查询参数与正文字段分开,并在端点(如前面提供的链接答案中所述)或单独的Pydantic模型中定义查询参数,如下所示。

工作示例3(基于此答案的方法3)

Swagger UI /docs中,由于data是一个Form参数并表示为单个字段,因此您需要将该字段中Base的数据作为字典传递,该字典将作为str提交到data Form参数中。测试示例:

{"boxes": [{"l": 0,"t": 0,"r": 0,"b": 0}], "comments": ["foo", "bar"], "code": 0}

有关如何测试的更多信息,请参阅上面的链接答案。

app.py

from fastapi import FastAPI, status, Form, UploadFile, File, Depends, Query
from pydantic import BaseModel, Field, ValidationError
from fastapi.exceptions import HTTPException
from fastapi.encoders import jsonable_encoder
from typing import Optional, List


app = FastAPI()


class BaseParams(BaseModel):
    width: Optional[float] = Field (...)
    height: Optional[float] = Field (...)
    words: List[str] = Field (Query(...))  #  wrap the `Query()` in a `Field()` for `List` params


class BaseBox(BaseModel):
    l: float=Field(...)
    t: float=Field(...)
    r: float=Field(...)
    b: float=Field(...)


class Base(BaseModel):
    boxes: List[BaseBox] = Field (...)
    comments: List[str] = Field (...)
    code: int = Field (...)
    

def checker(data: str = Form(...)):
    try:
        return Base.model_validate_json(data)
    except ValidationError as e:
        raise HTTPException(
            detail=jsonable_encoder(e.errors()),
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        )


@app.post("/submit")
def submit(
    base_params: BaseParams = Depends(),
    base: Base = Depends(checker),
    files: List[UploadFile] = File(...),
):
    return {
        "Params": base_params,
        "JSON Payload": base,
        "Filenames": [file.filename for file in files],
    }
工作示例4(基于此答案的方法4)

这种方法的优点是需要更少的代码来实现预期的结果,而且Base模型Swagger UI /docs的请求主体部分中表示(使用自动生成的输入示例),从而使数据视图更清晰,发布数据的方式更容易。同样,请查看上面的链接答案,了解有关此方法的更多详细信息。

app.py

from fastapi import FastAPI, Body, UploadFile, File, Depends, Query
from pydantic import BaseModel, Field, model_validator
from typing import Optional, List
import json


app = FastAPI()


class BaseParams(BaseModel):
    width: Optional[float] = Field (...)
    height: Optional[float] = Field (...)
    words: List[str] = Field (Query(...))  #  wrap the `Query()` in a `Field()` for `List` params

    
class BaseBox(BaseModel):
    l: float=Field(...)
    t: float=Field(...)
    r: float=Field(...)
    b: float=Field(...)


class Base(BaseModel):
    boxes: List[BaseBox] = Field (...)
    comments: List[str] = Field (...)
    code: int = Field (...)

    @model_validator(mode="before")
    @classmethod
    def validate_to_json(cls, value):
        if isinstance(value, str):
            return cls(**json.loads(value))
        return value


@app.post("/submit")
def submit(
    base_params: BaseParams = Depends(),
    base: Base = Body(...),
    files: List[UploadFile] = File(...),
):
    return {
        "Params": base_params,
        "JSON Payload": base,
        "Filenames": [file.filename for file in files],
    }







问答

如何在FastAPI中使用Pydantic的BaseModel上传文件和字典列表?

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
FastAPI ,可以使用 PydanticBaseModel 类来创建数据模型。如果你有一个字典类型的数据,可以通过将其作为关键字参数传递给 BaseModel 类来创建一个 BaseModel 实例。例如: ```python from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str price: float is_offer: bool = None item_data = {"name": "apple", "price": 2.5} item = Item(**item_data) print(item) ``` 输出: ``` name='apple' price=2.5 is_offer=None ``` 在上面的代码,我们定义了一个 Item 模型类,它继承自 BaseModel 类,并定义了三个属性:name、price 和 is_offer。然后,我们创建一个字典类型的数据 item_data,将其作为关键字参数传递给 Item 类的构造函数,从而创建一个 Item 实例 item。 如果字典类型的数据与模型类的属性不完全匹配,会引发 ValueError 异常。如果需要忽略字典多余的属性,可以设置模型类的 Config 类的 `extra` 属性为 `Extra.ignore`。例如: ```python from fastapi import FastAPI from pydantic import BaseModel, Extra app = FastAPI() class Item(BaseModel): name: str price: float is_offer: bool = None class Config: extra = Extra.ignore item_data = {"name": "apple", "price": 2.5, "color": "red"} item = Item(**item_data) print(item) ``` 输出: ``` name='apple' price=2.5 is_offer=None ``` 在上面的代码,我们在 Item 类定义了一个 Config 类,并设置了 extra 属性为 Extra.ignore。这意味着在创建 Item 实例时,会忽略字典多余的属性,不会引发异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坦笑&&life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值