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

aor9mmx1  于 5个月前  发布在  其他
关注(0)|答案(1)|浏览(184)

我有以下代码示例:

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 '[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

wd2eg0qa

wd2eg0qa1#

从您提供的代码中可以看出,您已经看过this answer了,这就是您最终应该找到的解决方案。
但是,让我解释一下你的例子中的代码有什么问题。你同时提交了文件和查询数据,或者,至少,这是你一直试图实现的。在端点中定义一个查询参数,例如,作为strint,或Pydantic模型沿着,并在端点中的参数上使用Depends()来指示BaseModel中定义的字段被期望作为查询参数,在这两种情况下,但是,当您直接在端点或BaseModel中将参数定义为List(例如List[int]List[str])时,您应该**使用Query**显式定义它,如herehere所解释和演示的。虽然Pydantic模型在过去不允许使用Query字段,并且必须在单独的依赖类中实现查询参数解析,如this answerthis answer中所示,这一点最近已经改变,因此,可以使用BaseModel类将Query() Package 在Field()中,如this answer中所示。

工作示例1

from fastapi import FastAPI, Query, Depends
from pydantic import BaseModel, Field
from typing import Optional, List

app = FastAPI()

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()):
    return base

字符串
在您的示例中,您似乎也有一个List查询参数,该参数需要一个字典/JSON对象列表作为值。然而,使用查询参数无法实现。如果您尝试在上面的工作示例中定义这样的参数(例如,boxes: List[BaseBox] = Field (Query(...))),则在尝试运行FastAPI应用程序时会遇到以下错误:
AssertionError:Param:boxes只能是请求体,使用Body()
如果您将参数定义为boxes: List[BaseBox] = Field (...),沿着在端点中定义file: UploadFile = File(...)(就像您在代码中已经做的那样),即使应用程序在尝试向该端点提交请求时会照常开始运行(例如,通过Swagger UI autodocs at /docs),您将收到一个422 Unprocessable Entity错误,其中包含一条具有类似含义的消息,说Input should be a valid dictionary or object to extract fields from
这是因为,在第一种情况下,查询参数不能期望字典数据(除非您遵循针对任意查询数据描述的herehere方法,您需要自己解析,我不建议这样做),而在第二种情况下,由于在端点中定义了file: UploadFile = File(...)字段,请求正文被编码为multipart/form-data发送;但是,HTTP协议不支持同时发送Form和JSON数据(再次参见this answer)。
但是,如果您从端点删除了UploadFile参数,则请求应该成功通过,因为请求主体将被终止为application/json(在提交请求时查看Swagger UI中的Content-Type请求头)。但是,在这种情况下,您应该使用POST请求-因此,使用@app.post('/')定义端点,例如,不是GET,因为带有GET/HEAD方法的请求不应该有主体(Requests using GET method should only be used to request data; they shouldn't include data)。

工作示例2

from fastapi import FastAPI, Query, Depends
from pydantic import BaseModel, Field
from typing import Optional, List

app = FastAPI()

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.post('/')
async def main(base: Base = Depends()):
    return base

同时发布File和JSON body(包括List字典)

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

工作示例3(基于this answer的方法3)

在Swagger UI /docs中,由于data是一个Form参数,并表示为单个字段,因此您需要将该字段中的Base数据作为字典传递,该字典将在dataForm参数中作为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],
    }

工作示例4(基于this answer的方法4)

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

    • 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],
    }

相关问题