如何使用FastAPI上传文件和JSON数据? [重复]

问题描述 投票:0回答:1

这是我的 Pydantic 模型:

class Base(BaseModel):
    name: str
    point: Optional[float] = None
    is_accepted: Optional[bool] = False
    

这是终点:

def create_base(
    base: Base = Form(...),
    file: List[UploadFile] = File(...)
):
...

我尝试通过多部分表单发送请求,但收到错误:

{
    "detail": [
        {
            "loc": [
                "body",
                "base"
            ],
            "msg": "value is not a valid dict",
            "type": "type_error.dict"
        }
    ]
} 

这是我的请求的有效负载:

{
  "name": "string",
  "point": 10.0,
  "is_accepted": true
}

我做错了什么?

python multipartform-data fastapi pydantic
1个回答
4
投票

更新

请查看此答案了解更多详细信息和选项。



根据 FastAPI 文档

您可以在路径操作中声明多个

Form
参数,但是 您 也不能将您期望收到的
Body
字段声明为
JSON
,如 该请求将使用以下方式对正文进行编码
application/x-www-form-urlencoded
代替
application/json
(当表单包含文件时,编码为
multipart/form-data
)。

这不是 FastAPI 的限制,它是

HTTP
协议的一部分。

方法1

因此,如此处所述,可以使用

File
Form
同时定义文件和表单字段。下面是一个工作示例:

app.py

from fastapi import Form, File, UploadFile, Request, FastAPI
from typing import List
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.post("/submit")
def submit(name: str = Form(...), point: float = Form(...), is_accepted: bool  = Form(...), files: List[UploadFile] = File(...)):
        return {"JSON Payload ": {"name": name, "point": point, "is_accepted": is_accepted}, "Filenames": [file.filename for file in files]}

@app.get("/", response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

您可以通过访问以下模板进行测试:http://127.0.0.1:8000

模板/index.html

<!DOCTYPE html>
<html>
   <body>
      <form method="post" action="http://127.0.0.1:8000/submit"  enctype="multipart/form-data">
         name : <input type="text" name="name" value="foo"><br>
         point : <input type="text" name="point" value=0.134><br>
         is_accepted : <input type="text" name="is_accepted" value=True><br>    
         <label for="file">Choose files to upload</label>
         <input type="file" id="files" name="files" multiple>
         <input type="submit" value="submit">
      </form>
   </body>
</html>

您还可以通过 OpenAPI 文档(Swagger UI)http://127.0.0.1:8000/docs 或 Python 请求进行测试,如下所示:

测试.py

import requests

url = 'http://127.0.0.1:8000/submit'
files = [('files', open('test_files/a.txt', 'rb')), ('files', open('test_files/b.txt', 'rb'))]
payload ={"name": "foo", "point": 0.13, "is_accepted": False}
resp = requests.post(url=url, data=payload, files = files) 
print(resp.json())

方法2

可以使用 Pydantic 模型以及 Dependency 来通知“提交”路由(在下面的情况下)参数化变量

base
取决于
Base
类。请注意,此方法需要
base
数据作为查询(而不是正文)参数(然后使用
.dict()
方法将其转换为等效的 JSON 有效负载),并将文件作为正文中的
multipart/form-data

app.py

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

app = FastAPI()
templates = Jinja2Templates(directory="templates")

class Base(BaseModel):
    name: str
    point: Optional[float] = None
    is_accepted: Optional[bool] = False

@app.post("/submit")
def submit(base: Base = Depends(), files: List[UploadFile] = File(...)):
    received_data= base.dict()
    return {"JSON Payload ": received_data, "Uploaded Filenames": [file.filename for file in files]}
 
@app.get("/", response_class=HTMLResponse)
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

再次,您可以使用下面的模板进行测试:

模板/index.html

<!DOCTYPE html>
<html>
   <body>
      <form method="post" id="myForm" onclick="transformFormData();" enctype="multipart/form-data">
         name : <input type="text" name="name" value="foo"><br>
         point : <input type="text" name="point" value=0.134><br>
         is_accepted : <input type="text" name="is_accepted" value=True><br>    
         <label for="file">Choose files to upload</label>
         <input type="file" id="files" name="files" multiple>
         <input type="submit" value="submit">
      </form>
      <script>
         function transformFormData(){
            var myForm = document.getElementById('myForm');
            var qs = new URLSearchParams(new FormData(myForm)).toString();
            myForm.action = 'http://127.0.0.1:8000/submit?'+qs;
         }
      </script>
   </body>
</html>

如前所述,您可以使用 Swagger UI,或下面的 Python 请求示例:

测试.py

import requests

url = 'http://127.0.0.1:8000/submit'
files = [('files', open('test_files/a.txt', 'rb')), ('files', open('test_files/b.txt', 'rb'))]
payload ={"name": "foo", "point": 0.13, "is_accepted": False}
resp = requests.post(url=url, params=payload, files=files)
print(resp.json())
© www.soinside.com 2019 - 2024. All rights reserved.