我有以下代码示例:

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文档,但我总是收到错误.你认为我必须发送2个文件吗? 我也试过了

curl -X 'POST' \
  'http://localhost:8007/submit?width=10&height=10' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@test.png;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.

推荐答案

如您提供的代码所示,您已经了解了this answer,这就是您最终应该找到您正在寻找的解决方案的地方.

但是,让我解释一下您的示例中的代码有什么问题.您同时提交了文件和查询数据,或者,至少这似乎是您一直在努力实现的目标.在端点中将查询参数定义为例如strint,或者在端点中的参数上使用Depends()来指示在BaseModel中定义的文件被期望作为查询参数,在这两种情况下,它都应该工作得很好.但是,当您直接在端点或BaseModel中将参数定义为List(例如List[int]List[str])时,您应该将其定义为define it explicitly with Query,如herehere所解释和演示的那样.虽然在过go ,Pydtic模型不允许使用Query个字段,并且必须在单独的依赖类中实现查询参数解析,如this answerthis answer所示,但这一点最近已被更改,因此,您可以使用BaseModel类将Query()包装在Field()中,如this answer中所示.

工作示例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对象列表.然而,这cannot是使用查询参数来实现的.如果您try 在上面的工作示例中定义这样的参数(例如boxes: List[BaseBox] = Field (Query(...))),则在try 运行FastAPI应用程序时会遇到以下错误:

AssertionError:param:boxes can only be a request body,使用 Body()

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

这是因为,在第一种情况下,您不能有一个需要字典数据的查询参数(除非您对任意查询数据遵循了描述herehere的方法,您需要自己解析,我不建议这样做),而在第二种情况下,由于端点中定义了file: UploadFile = File(...),请求正文被编码为multipart/form-data发送;然而,HTTP协议不支持同时发送表单和JSON数据(同样,请参见this answer).

但是,如果您从端点删除了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

Posting both File(s) and JSON body (including List of dictionaries)

如果您仍然需要在FastAPI POST请求中添加both个文件(S)和JSON Body,我强烈建议您看看this answer个中的第Methods 3 and 4个.下面提供的示例基于链接答案中的这两种方法,并演示了如何将文件与还包括词典列表的JSON数据一起发布,就像您的示例中一样.有关如何测试这些方法的更多细节和示例,请查看链接的答案.在您的例子中,您需要将查询参数与正文字段分开,并在端点中定义查询参数(如前面提供的链接答案中所述),或在单独的Pydtic模型中定义查询参数,如下所示.

Working Example 3 (based on Method 3 of this answer)

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

{"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],
    }

Working Example 4 (based on Method 4 of this answer)

这种方法的优点是需要更少的代码来实现预期的结果,并且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],
    }

Python相关问答推荐

从今天起的future 12个月内使用Python迭代

指示组内的rejected_time是否在creation_timestamp后5分钟内

如何使用entry.bind(FocusIn,self.Method_calling)用于使用网格/列表创建的收件箱

在应用循环中间保存pandas DataFrame

如何在Python中使用时区夏令时获取任何给定本地时间的纪元值?

Python会扔掉未使用的表情吗?

Python多处理:当我在一个巨大的pandas数据框架上启动许多进程时,程序就会陷入困境

从收件箱中的列中删除html格式

不理解Value错误:在Python中使用迭代对象设置时必须具有相等的len键和值

Pandas - groupby字符串字段并按时间范围 Select

Pandas—合并数据帧,在公共列上保留非空值,在另一列上保留平均值

如果值发生变化,则列上的极性累积和

Python中的变量每次增加超过1

基于形状而非距离的两个numpy数组相似性

如何在PySide/Qt QColumbnView中删除列

在方法中设置属性值时,如何处理语句不可达[Unreacable]";的问题?

OpenCV轮廓.很难找到给定图像的所需轮廓

如何在Great Table中处理inf和nans

为罕见情况下的回退None值键入

如果服务器设置为不侦听创建,则QWebSocket客户端不连接到QWebSocketServer;如果服务器稍后开始侦听,则不连接