如何在FastAPI中POST文件和JSON数据列表?

krugob8w  于 2023-10-21  发布在  其他
关注(0)|答案(1)|浏览(132)

我正在使用Pydantic模型在FastAPI中创建一个注册API。在注册时,用户需要输入自己的文件,用户信息和学校信息。由于用户应该能够输入多个学校,我正在考虑将学校信息作为列表传递。根据我的研究,在同一个API中似乎不支持上传文件和发送JSON格式的数据。因此,我发现我需要以form_data而不是JSON格式(How to add both file and JSON body in a FastAPI POST request?)发送数据。我可以转换用户信息并将其作为表单数据发送,但我不确定如何传递学校信息列表。到目前为止,我所尝试的如下。
我使用了一个类方法来接收表单数据中的User信息。

class User(BaseModel):
    name: str 
    password:  str 
    email: str

    @classmethod
    def as_form(cls, name: str = Form(...), password:  str= Form(...),
                email:  str= Form(...)) -> 'User':
        return cls(name=first_name,  password=password, email=email)

这是我想要的模型作为一个列表。

class School(BaseModel):
    school: str 
    country: str

这是API。

@router.post("/signup", status_code=status.HTTP_201_CREATED)
async def signup(hospitals: List[School] = Form(...),
                 user: User = Depends(UserIn.as_form),
                 file: UploadFile = File(),session: Session = Depends(db.session)):

我试过schools: Annotated[Json[School], Form]
我还尝试为School模型创建一个as_form类方法,就像我为User模型所做的那样,以便将其作为这样的列表传递。然而,这对两个人都不起作用。此外,以列表形式传递表单数据似乎不是一种常用的方法。
schools: List[School] = Depends(School.as_form)
我不知道该怎么办。我考虑过以JSON格式接收数据并在服务器上解析它,但在这种情况下,我应该如何定义Pydantic模型?我想知道在我目前的情况下最理想的方法。你能帮帮我吗?

gxwragnw

gxwragnw1#

解决这个问题的一种方法是有两个单独的List参数,一个用于schools,另一个用于countries。例如:

@router.post("/signup")
async def signup(schools: List[str] = Form(...), countries: List[str] = Form(...))
    pass

然而,由于这样的解决方案需要在后端进行额外的工作来将两个列表形成一个等等,并且如果需要添加更多的列表,事情会变得更加复杂,因此下面也给出了以下解决方案。
下面提供的解决方案1和2分别主要基于this answer方法3和4。因此,请参阅该答复以了解进一步的细节和解释。这两个解决方案都演示了如何使用单个POST请求在FastAPI中上传FileList JSON数据。下面还提供了Python requests和JavaScript Fetch API中的客户端示例。

解决方案一

app.py

from fastapi import FastAPI, status, Form, UploadFile, File, Depends, Request
from pydantic import BaseModel, ValidationError
from fastapi.exceptions import HTTPException
from fastapi.encoders import jsonable_encoder
from typing import Optional, List
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

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

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

def checker(data: List[str] = Form(...)):
    models = []
    try:
        for d in data:
            model = Base.parse_raw(d)
            models.append(model)
    except ValidationError as e:
        raise HTTPException(
            detail=jsonable_encoder(e.errors()),
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        )

    return models

@app.post("/submit")
def submit(models: List[Base] = Depends(checker), files: List[UploadFile] = File(...)):
    return {"JSON Payload ": models, "Filenames": [file.filename for file in files]}

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

方案二

app.py

from fastapi import FastAPI, File, Body, UploadFile, Request
from pydantic import BaseModel, model_validator
from typing import Optional, List
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import json

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

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

    @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(data: List[Base] = Body(...), files: List[UploadFile] = File(...)):
    return {"JSON Payload ": data, "Filenames": [file.filename for file in files]}

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

上述测试溶液1和2

使用Python请求

test.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'))]
items = ['{"name": "foo", "point": 0.11, "is_accepted": false}', '{"name": "bar", "point": 0.42, "is_accepted": true}']
data = {'data': items}
resp = requests.post(url=url, data=data, files=files) 
print(resp.json())

使用JavaScript Fetch API

templates/index.html

<!DOCTYPE html>
<html>
   <body>
      <input type="file" id="fileInput" name="file" onchange="reset()" multiple><br>
      <input type="button" value="Submit using fetch" onclick="submitUsingFetch()">
      <p id="resp"></p>
      <script>
         function reset() {
            var resp = document.getElementById("resp");
            resp.innerHTML = "";
            resp.style.color = "black";
         }
         
         function submitUsingFetch() {
            var resp = document.getElementById("resp");
            var fileInput = document.getElementById('fileInput');
            if (fileInput.files[0]) {
               var formData = new FormData();
               var items = [];
         
               items[0] = JSON.stringify({"name": "foo", "point": 0.11, "is_accepted": false});
               items[1] = JSON.stringify({"name": "bar", "point": 0.42, "is_accepted": true});  
         
               for (const i of items)
                  formData.append("data", i);
         
               for (const file of fileInput.files)
                  formData.append('files', file);
         
               fetch('/submit', {
                     method: 'POST',
                     body: formData,
                  })
                  .then(response => response.json())
                  .then(data => {
                     resp.innerHTML = JSON.stringify(data); // data is a JSON object
                  })
                  .catch(error => {
                     console.error(error);
                  });
            } else {
               resp.innerHTML = "Please choose some file(s)...";
               resp.style.color = "red";
            }
         }
      </script>
   </body>
</html>

相关问题