20

I am using FastAPI to upload file according to the official documentation, just like:

@app.post("/create_file/")
async def create_file(file: UploadFile=File(...)):
      file2store = await file.read()
      # some code to store the BytesIO(file2store) to the other database

When I send a request using Python requests lib:

f = open(".../file.txt", 'rb')
files = {"file": (f.name, f, "multipart/form-data")}
requests.post(url="SERVER_URL/create_file", files=files)

the file2store is always empty. Sometimes (rarely seen), it can get the file bytes, but almost all the time it is empty, so I can't restore the file on the other database.

I also tried the bytes rather than UploadFile, I get the same results. Is there something wrong in my code, or is the way I use the FastAPI to upload file wrong? I googled it for some answers for a long time, but failed. So I raise the problem here, hoping someone who knows the answer can help me out.

Chris
  • 4,940
  • 2
  • 7
  • 28
Aric
  • 201
  • 1
  • 2
  • 3
  • Do you have `python-multipart` installed? if it's not go for `pip install python-multipart` – Yagiz Degirmenci Jul 23 '20 at 07:20
  • yes, I have installed that. Sometimes I can upload successfully, but it happened rarely. – Aric Jul 23 '20 at 07:26
  • Is it happening on a specific file type? – Yagiz Degirmenci Jul 23 '20 at 07:41
  • I tried docx, txt, yaml, png file, all of them have the same problem. And I just found that when I firstly upload a new file, it can upload successfully, but when I upload it at the second time (or more), it failed. – Aric Jul 23 '20 at 08:01
  • 1
    I know the reason. Thanks for inspiring me. I just use `f = open(file)` method once, and when I send the request several times, the f will be closed after the first time. Thank you agin for helping me. – Aric Jul 23 '20 at 08:21

3 Answers3

14

Note that the answer below uses synchronous writing for writing the files to disk. If you need async writing, please have a look at this answer. Moreover, if you need to send additional data (such as JSON data) together with uploading the file(s), please have a look at this answer. I would suggest you have a look at this answer as well, which explains the difference between defining an endpoint with def and async def, and demonstrates how to read the contents of an uploaded file when using def (if that's a requirement for your app).

Upload Single File

app.py

import uvicorn
from fastapi import File, UploadFile, FastAPI

app = FastAPI()

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

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)

test.py

import requests

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

Upload Multiple (List of) Files

app.py

import uvicorn
from fastapi import File, UploadFile, FastAPI
from typing import List

app = FastAPI()

@app.post("/upload")
async def upload(files: List[UploadFile] = File(...)):
    for file in files:
        try:
            contents = await file.read()
            with open(file.filename, 'wb') as f:
                f.write(contents)
        except Exception:
            return {"message": "There was an error uploading the file(s)"}
        finally:
            await file.close()

    return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}    

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)

test.py

import requests

url = 'http://127.0.0.1:8000/upload'
files = [('files', open('images/1.png', 'rb')), ('files', open('images/2.png', 'rb'))]
resp = requests.post(url=url, files=files) 
print(resp.json())
Chris
  • 4,940
  • 2
  • 7
  • 28
6
@app.post("/create_file/")
async def image(image: UploadFile = File(...)):
    print(image.file)
    # print('../'+os.path.isdir(os.getcwd()+"images"),"*************")
    try:
        os.mkdir("images")
        print(os.getcwd())
    except Exception as e:
        print(e) 
    file_name = os.getcwd()+"/images/"+image.filename.replace(" ", "-")
    with open(file_name,'wb+') as f:
        f.write(image.file.read())
        f.close()
   file = jsonable_encoder({"imagePath":file_name})
   new_image = await add_image(file)
   return {"filename": new_image}
mohd sakib
  • 85
  • 1
  • 3
-1

I had worked on it nearly 7-8 months ago, I didn't encounter the problem which you are saying. I had found out that that UploadFile is of type SpooledTemproaryFile and I didn't try reading file asynchronously. Here is the snippet which is being used. I have uploaded all types of files up to 100mb, it is working. I had to save file on server on a specific path

with open(path, 'wb') as f:
    [f.write(chunk) for chunk in iter(lambda: file.file.read(10000), b'')]

In your case I'd suggest reading file and making the post request directly using aiohttp sessions or AsyncClient from httpx

yogesh
  • 96
  • 1
  • 7