'How to include JSON and File data together in FastAPI endpoint?

I would like to POST JSON and File data together, as shown in the code below:

fastapi.py

@router.post('/rate')
def users(user_review:schemas.Rate, image123: UploadFile = File(...), db: Session=Depends(get_db)):
    print(image123)

schemas.py

class Rate(BaseModel):
    id1:int
    id2:int
    message:Optional[str] = None
    rate:conint(ge=1, le=5)

However, when I execute it, it throws the following 422 error:

{
    "detail": [
        {
            "loc": [
                "body",
                "user_review"
            ],
            "msg": "field required",
            "type": "value_error.missing"
        },
        {
            "loc": [
                "body",
                "image123"
            ],
            "msg": "field required",
            "type": "value_error.missing"
        }
    ]
}


Solution 1:[1]

You can't declare a path operation (endpoint) that expects both JSON and File/Form data together, as this is not supported by the HTTP protocol, as explained in FastAPI's documentation. When a request includes Form data, it will have the body encoded using application/x-www-form-urlencoded instead of application/json, as well as if File data are included too, it will have the body encoded using multipart/form-data.

As explained in this answer, there are various other methods to send additional data together with uploading Files. The below demonstrates an approach based on Method 4 of the above answer.

Note: You shouldn't name your python script file fastapi.py (as shown in your question), as this would interfere with the library (when using, for example, from fastapi import FastAPI), but rather use some neutral name, such as app.py.

app.py

from fastapi import FastAPI, File, UploadFile, Body
from pydantic import BaseModel, conint
from typing import Optional
import json

app = FastAPI()

class Rate(BaseModel):
    id1: int
    id2: int
    message: Optional[str] = None
    rate: conint(ge=1, le=5)

    @classmethod
    def __get_validators__(cls):
        yield cls.validate_to_json

    @classmethod
    def validate_to_json(cls, value):
        if isinstance(value, str):
            return cls(**json.loads(value))
        return value
    
@app.post("/rate")
def submit(user_review: Rate = Body(...), image: UploadFile = File(...)):
        return {"JSON Payload ": user_review, "Image": image.filename}

test.py

import requests

url = 'http://127.0.0.1:8000/rate'
file = {'image': open('image.png','rb')}
data = {'user_review': '{"id1": 1, "id2": 2, "message": "foo", "rate": 5}'}
resp = requests.post(url=url, data=data, files=file) 
print(resp.json())

Test with Fetch API (using Jinja2Templates)

<html>
   <head>
      <script type="text/javascript">       
         function submitData() {
            var fileInput = document.getElementById('imageFile');
            if (fileInput.files[0]) {
                var data = new FormData();
                data.append("user_review", JSON.stringify({id1: 1, id2: 2, message: "foo", rate: 5}));
                data.append("image", fileInput.files[0]);
                fetch('/rate', {
                        method: 'POST',
                        headers: {
                            'Accept': 'application/json'
                        },
                        body: data
                    })
                    .then(response => response.text())
                    .then(data => {
                        document.getElementById("responseArea").innerHTML = data;
                    })
                    .catch(error => {
                        console.error(error);
                    });
            }
         }
      </script>
   </head>
   <body>
      <input type="file" id="imageFile" name="file"></br></br>
      <input type="button" value="Submit" onclick="submitData()">
      <div id="responseArea"></div>
   </body>
</html>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1