POST 使用 Python 带有文件和查询参数的请求向 FastAPI 请求

POST request to FastAPI using Python Requests with a file and query parameters

我正在使用 FastAPI 为一些 ML 模型提供服务,我有一个 Streamlit 基本 UI 使用 Python Requests 模块。

我的一项服务是通过 POST 请求获取图像,效果非常好。

服务器端

@app.post("/segformer")
async def segformer(file: Optional[UploadFile] = None):

由 {BASE_URI}/docs

给出的卷曲
curl -X 'POST' \
  'http://localhost:8080/segformer' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@image1.jpg;type=image/jpeg'

客户端使用 Python 请求

response = requests.post(f"{BASE_URI}/segformer", files=files)

一旦我想添加额外的参数,它就会变得令人毛骨悚然。例如:

服务器端

@dataclass
class ModelInterface:
    model_name: str = "detr"
    tags: List[str] = Query(...)

@app.post("/detr")
async def detr(file: UploadFile = File(...), model: ModelInterface = Depends()):

curl -- 由 {BASE_URI}/docs

给出
curl -X 'POST' \
  'http://localhost:8080/detr?model_name=detr&tags=balloon&tags=dog' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@image1.jpg;type=image/jpeg'

到那时一切正常。一旦我想使用 Python Requests 转换该调用,我就会遇到一些问题,这些问题总是以 /detr HTTP/1.1" 400 Bad Request 错误告终。

这是我试过的:

客户端使用 Python 请求

headers = {
    "Content-type": "multipart/form-data",
    "accept": "application/json"
}

payload = {
    "tags": ["balloon", "dog"]
}

response = requests.post(
    f"{BASE_URI}/detr",
    headers=headers,
    json=payload,  <- also *data*  
    files=files,
)

最后似乎问题缩小了如何转换这个:

curl -X 'POST' \
  'http://localhost:8080/detr?tags=balloon&tags=dog' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@image1.jpg;type=image/jpeg'

一个有效的 Python Requests 调用!

我还遇到了以下 FastAPI 代码的问题:

@dataclass
class ModelInterface:
    model_name: str = "detr"
    tags: List[str] = None

@app.post("/detr2")
async def detr2(file: UploadFile = File(...), model: ModelInterface = Form(...)):
...

转换为这个 curl 命令:

curl -X 'POST' \
  'http://localhost:8080/detr2' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@image1.jpg;type=image/jpeg' \
  -F 'model={
  "model_name": "detr",
  "tags": [
    "balloon", "dog"
  ]
}' 

失败并出现 "POST /detr2 HTTP/1.1" 422 Unprocessable Entity 错误

要在 Python 请求中传递查询参数,您应该改用 params 键。因此:

response = requests.post(url='<your_url_here>', params=payload)

此外,无需在 header 中设置 Content-type,因为它会根据您传递给 requests.post() 的参数自动添加。这样做,请求将失败,因为除了 multipart/form-dataContent-type 必须 包括使用的 boundary 值描述 post body 中的部分。不设置 Content-Type header 确保请求将其设置为正确的值 " (ref). Have a look at this and this。此外,请确保在 files 中使用与在端点中提供的相同 key 名称,即 file。因此,在 Python 请求中它应该看起来像像下面这样:

files = {('file', open('my_file.txt', 'rb'))}
response = requests.post(url='<your_url_here>', files=files, params=payload)

您还可以在 this answer.

找到更多关于如何将附加数据与文件一起发送的选项