如您提供的代码所示,您已经了解了this answer,这就是您最终应该找到您正在寻找的解决方案的地方.
但是,让我解释一下您的示例中的代码有什么问题.您同时提交了文件和查询数据,或者,至少这似乎是您一直在努力实现的目标.在端点中将查询参数定义为例如str
或int
,或者在端点中的参数上使用Depends()
来指示在BaseModel
中定义的文件被期望作为查询参数,在这两种情况下,它都应该工作得很好.但是,当您直接在端点或BaseModel
中将参数定义为List
(例如List[int]
或List[str]
)时,您应该将其定义为define it explicitly with Query
,如here和here所解释和演示的那样.虽然在过go ,Pydtic模型不允许使用Query
个字段,并且必须在单独的依赖类中实现查询参数解析,如this answer和this 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
.
这是因为,在第一种情况下,您不能有一个需要字典数据的查询参数(除非您对任意查询数据遵循了描述here和here的方法,您需要自己解析,我不建议这样做),而在第二种情况下,由于端点中定义了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],
}