1

I have API which accepts JSON data and file (png).

This is my fastapi code:

@app.post("/api/")
async def dummy_file_return(metadata=Body(...),file=File(...)):
  print("content_type is")
  print(file.content_type)

When accesing with curl.

$curl -X POST -F file=@0_for_django_testcase.png -F metadata='{"meta":"test"}' localhost:8008/api/

server log shows,

content_type is
image/png

I can see content_type is guessed automatically and image/png is set.

Then, I tried the same thing by requests of python.

    response = requests.post(
        url,
        data={"metadata":json.dumps({"meta":"test")},
        files = {
            "file": open('0_for_django_testcase.png','rb')
        },
    )

console shows

content_type is

content_type is empty and nothing appears.

Why this difference occurs?

In either way file upload is successed, however there is difference for content_type.

I don't set any header for curl though, -F flag secretly send some headers?

Another trial, I tested these patterns with headers, but both returns <Response [400]> error.

    response = requests.post(
        url,
        data={"metadata":json.dumps({"meta":"test")},
        files = {
            "file": open('0_for_django_testcase.png','rb')
        },
        headers={
            "Content-Type":"image/png"
        }
    )


    response = requests.post(
        url,
        data={"metadata":json.dumps({"meta":"test")},
        files = {
            "file": open('0_for_django_testcase.png','rb')
        },
        headers={
            "Content-Type":"multipart/form-data"
        }
    )

Any help appreciated.

Chris
  • 18,724
  • 6
  • 46
  • 80
whitebear
  • 11,200
  • 24
  • 114
  • 237
  • `curl` guesses the mime type for you. See [How does `curl` on the command line determine the MIME type of a file being uploaded?](https://unix.stackexchange.com/questions/396385/how-does-curl-on-the-command-line-determine-the-mime-type-of-a-file-being-uplo). – Amadan Jul 09 '23 at 16:54

1 Answers1

2

First, I would suggest having a look at this answer, which provides detailed information on how to upload file(s) in FastAPI. Second, you don't seem to be sending JSON data along with the file, but rather Form data. That is the case when using the data argument in requests, regardless of defining the parameter with Body() in the FastAPI endpoint, as well as using json.dumps() to serialise the value of the metadata key. You would also need to set the request's Content-Type to application/json, or simply use the json argument instead (see here, as well as here and here). However, since you attempt sending both file and JSON data, it would not work—please have a look at this answer as to why your approach would not work, as well as how to achieve that. Hence, the example you provided is working simply because you send Form data along with the file, which is valid for the multipart/form-data content type (see here as well on how to send multipart/form-data using Python requests).

As for the file's content_type (better known as media type in this case, and formerly known as MIME type, see MDN's documentation here and here) not being received, this has to do with the requests module. In cURL this is guessed based on filename extension matching (note that there is a built-in Python module, called mimetypes, which offers similar capabilities—examples are provided in the last section of this answer). In requests, however, you could manually set the content_type for files when sending the request—see the relevant documentation here and here (have a look at the files argument) for more details, as well as this answer. If you would like to avoid that, you could instead use httpx, which would automatically set the content_type for you, and still allow you to manually change it in a similar way to requests, if you desire, as described here (related posts using httpx in FastAPI that might also be helpful can be found here and here). Both options are provided below.

Working Example

app.py

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/upload")
def upload(file: UploadFile = File(...)):
    try:
        print(f'Content type of file: {file.content_type}')
        contents = file.file.read()
        with open(file.filename, 'wb') as f:
            f.write(contents)
    except Exception:
        return {"message": "There was an error uploading the file"}
    finally:
        file.file.close()

    return {"message": f"Successfully uploaded {file.filename}"}    

test.py (using requests)

import requests

url = 'http://127.0.0.1:8000/upload'
file = {'file': ('1.png', open('images/1.png', 'rb'), 'image/png')}
r = requests.post(url=url, files=file) 
print(r.json())

test.py (using httpx)

import httpx

url = 'http://127.0.0.1:8000/upload'
file = {'file': open('images/1.png', 'rb')}
r = httpx.post(url=url, files=file) 
print(r.json())
Chris
  • 18,724
  • 6
  • 46
  • 80