2

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"
        }
    ]
}
3
  • 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. Commented Feb 14, 2022 at 8: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. Commented Feb 14, 2022 at 9:55
  • 2
    Does this answer your question? How to add both file and JSON body in a FastAPI POST request? Commented Jan 12, 2023 at 11:16

1 Answer 1

4

Update

This question is duplicate of this one. Please take a look at that link for more details and examples.

Original Answer

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, model_validator
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)

    @model_validator(mode='before')
    @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>
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.