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.
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.
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())