1

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"
        }
    ]
}
Chris
  • 18,724
  • 6
  • 46
  • 80
Raj Shah
  • 48
  • 3
  • 10
  • The error message is telling you that you haven't included any values for those fields - your request does not match what the API expects. You can use `response_model` for the endpoint to tell FastAPI how to format what you return from the view function - in your case you're not returning anything - so there is no response to format either. – MatsLindh Feb 14 '22 at 08:35
  • 2
    @MatsLindh The issue is not only that values for the required fields were not included in the request, but also, that the endpoint expects `JSON` data and `form-data` at the same time, which is not possible, as explained in the link provided above. – Chris Feb 14 '22 at 09:55
  • 1
    Does this answer your question? [How to add both file and JSON body in a FastAPI POST request?](https://stackoverflow.com/questions/65504438/how-to-add-both-file-and-json-body-in-a-fastapi-post-request) – Aadarsha Jan 12 '23 at 11:16

1 Answers1

4

You can't declare an 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; if File data are included as well, it will have the body encoded using multipart/form-data.

As explained in this answer, there are various 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 or HTMLResponse)

<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(resp => resp.text())  // or, resp.json(), etc.
                    .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>
Chris
  • 18,724
  • 6
  • 46
  • 80