FastAPI: Simple File Upload
How to upload and save multiple files with FastAPI
Created Aug 27, 2020 - Last updated: Aug 27, 2020
fastapi
api
upload
snippet
tutorial
engineering
development
This is something that took me a while to figure out, and I’m surprised why no one put out a simple tutorial for this.
So here’s what we’re going to do today.
- Write an API to upload “files” to the web server local directory
- Each request will have a unique request ID folder (this helps prevent same filename collision)
- Manage file Path in a “clean” manner
Let’s get right into it.
Imports
Here’s what you will need to import:
import os
import shutil
import uuid
from pathlib import Path
from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
Request Body
Here’s your actual API body:
@app.post("/upload/files/")
async def create_upload_files(files: List[UploadFile] = File(...)):
# base directory
WORK_DIR = Path(config.get('WORK_DIR'))
# UUID to prevent file overwrite
REQUEST_ID = Path(str(uuid.uuid4())[:8])
# 'beautiful' path concat instead of WORK_DIR + '/' + REQUEST_ID
WORKSPACE = WORK_DIR / REQUEST_ID
if not os.path.exists(WORKSPACE):
# recursively create workdir/unique_id
os.makedirs(WORKSPACE)
# iterate through all uploaded files
for file in files:
FILE_PATH = Path(file.filename)
WRITE_PATH = WORKSPACE / FILE_PATH
with open(str(WRITE_PATH) ,'wb') as myfile:
contents = await file.read()
myfile.write(contents)
# return local file paths
return {"file_paths": [str(WORKSPACE)+'/'+file.filename for file in files]}
@app.get("/")
async def main():
content = """
<body>
<form action="/upload/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
Download Structure
Which will download your files in the following tree:
➜ simple-fastapi-upload git:(master) ✗ tree
.
├── data # WORK_DIR
│ ├── 366b2e73 # unique to each request
│ │ ├── image-picker.css # files uploaded in that request
│ │ ├── image-picker.js
│ │ ├── index.html
│ │ └── jquery-3.0.0.min.js
│ ├── 9bce9738
│ │ ├── image-picker.css
│ │ ├── image-picker.js
│ │ ├── index.html
│ │ └── jquery-3.0.0.min.js
│ ├── a0ac30a3
│ │ ├── image-picker.css
│ │ ├── image-picker.js
│ │ ├── index.html
│ │ └── jquery-3.0.0.min.js
│ └── ff400b65
│ ├── image-picker.css
│ ├── image-picker.js
│ ├── index.html
│ └── jquery-3.0.0.min.js
Code
Tying it all together:
import os
import shutil
import uuid
from pathlib import Path
from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
config = {
'WORK_DIR':'data/'
}
@app.post("/upload/files/")
async def create_upload_files(files: List[UploadFile] = File(...)):
# base directory
WORK_DIR = Path(config.get('WORK_DIR'))
# UUID to prevent file overwrite
REQUEST_ID = Path(str(uuid.uuid4())[:8])
# 'beautiful' path concat instead of WORK_DIR + '/' + REQUEST_ID
WORKSPACE = WORK_DIR / REQUEST_ID
if not os.path.exists(WORKSPACE):
# recursively create workdir/unique_id
os.makedirs(WORKSPACE)
# iterate through all uploaded files
for file in files:
FILE_PATH = Path(file.filename)
WRITE_PATH = WORKSPACE / FILE_PATH
with open(str(WRITE_PATH) ,'wb') as myfile:
contents = await file.read()
myfile.write(contents)
# return local file paths
return {"file_paths": [str(WORKSPACE)+'/'+file.filename for file in files]}
@app.get("/")
async def main():
content = """
<body>
<form action="/upload/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
@app.get("/ping")
def ping():
return {"message": "pong"}
That’s about it!
I’m always learning, so if you spot an error or find an optimization in any of the content feel free mention it in the comments so that I can fix it for everyone :)
If you face any issues with the file upload snippet, drop a comment and I’ll try to help.
Peace ✌🏾