Search code examples
pythoncurlpython-requestsfastapi

Content-type is different when sending file to FastAPI backend using cURL than Python requests


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.


Solution

  • 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 of requests.post() function, regardless of defining the metadata parameter on server side with Body() and using json.dumps() on client side to serialize the value for that parameter.

    On client side, you would also need to set the request's Content-Type to application/json, or simply use the json argument instead of data (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 solve 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())