Data exchange is integral to web applications, and the art of handling file uploads, conversion, and downloads is crucial for developers. The application part of managing files comes heavily into play in applications that deal with user-generated content.
Grab a cup of coffee, get comfortable, and relax. This article will guide you through mastering FastAPI’s file-handling features. From basics to advanced techniques, we’ll cover everything you need to know about managing file uploads and downloads in your applications. Enjoy the learning journey! Here is a brief summary of what we are going to cover in this article.
What is FastApi?
is a high-performing web framework for building APIs with Python 3.7+ based on standard Python-type hints. It helps developers build applications quickly and efficiently. FastAPI is built on top of the Starlette web server and includes features that make building web applications easier, such as automatic data validation, error handling, and interactive API docs.
FastAPI’s performance is frequently likened to popular scripting languages like GO and Node.js, and the consensus is clear: it’s outstanding. Its speed and efficiency make coding with FastAPI smooth and enjoyable.
Enough talking let's dive deep into code and create a FastApi environment.
Navigate to Your Project Directory:
Open up a terminal in your system and navigate to your projects directory. For example:
cd path/to/your/project/directory
Create a Virtual Environment
python -m venv venv_name
Replace venv_name
with the desired name for your virtual environment.
Activating the Virtual Environments of Different Systems
macOS or Linux
source venv_name/bin/activate
Windows
venv_name\Scripts\activate
If the virtual environment was properly configured, you will see something like this in your terminal:
(myenv) user@your-computer:~/path/to/your/project/directory$
FastApi Installation
The only requirement you need to have is a Python interpreter that is either 3.8 or greater.
Install the following packages using pip:
pip install fastapi
You will also need an ASGI server, and that's where uvicorn kicks in
pip install "uvicorn[standard]"
Testing out if our Server works by implementing a small API:
Create a file called main.py
and paste the following code into that file:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
To run the server type this command in the terminal:
uvicorn main:app --reload
After clicking the port number displayed in the terminal, you should observe a similar display in your web browser.
💡 Speed up your blog creation with DifferAI.
Available for free exclusively on the free and open blogging platform, Differ.
Handling File Uploads
Option one
There are two methods for uploading files, but one of them has a limitation. Let’s examine both, and you can choose the most suitable one for your needs.
In your main.py
, let’s create a method called get_file
which will be used to test out one of the methods
import shutil
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
@app.post('/files')
def get_file(file: bytes = File(...)):
content = file.decode('utf-8')
lines = content.split('\n')
return {"content": lines}
Note: You need to install a package called python-multipart before running your server again
pip install python-multipart
python-multipart
is a Python library that simplifies the handling of HTTP multipart requests, which are commonly used for uploading files through APIs. This library is especially useful when working with frameworks like FastAPI or Flask, where handling file uploads is a common requirement.
Breakdown of the code above:
@app.post('/files'):
This line is a FastAPI decorator indicating that the following function will handle POST requests to the ‘/files’ endpoint.
def get_file(file: bytes = File(...)):
This defines a function namedget_file
that takes a single parameterfile
.file: bytes
specifies that thefile
parameter is expected to be of typebytes
.= File(...)
indicates that FastAPI will automatically parse the incoming request and extract the file data. TheFile(...)
dependency is used for handling file uploads in FastAPI.content = file.decode('utf-8'):
This line decodes the binary file data (file
) into a UTF-8 encoded string, converting the bytes into a human-readable format.decode('utf-8')
is used to decode the binary data into a string using the UTF-8 character encoding.
lines = content.split('\n'):
This line splits the decoded content into a list of lines based on the newline character ('\n'
).
split('\n')
is a string method that divides the string into a list of substrings at each occurrence of the specified newline character.
return {"content": lines}
: This line constructs a dictionary with a key "content"
and the list of lines (lines
) as its value.
- The function returns this dictionary as the response of the API endpoint.
- Clients accessing this endpoint will receive a JSON response containing the content of the uploaded file split into lines.
FastApi Interactive Docs
To actually test it out if its working, we will be using FastApi’s interactive docs.
FastAPI generates a “schema” with all your APIs using the OpenAPI standard for defining APIs. A “schema” is a definition or description of something. Not the code that implements it, but just an abstract description. The OpenAPI schema is what powers the two interactive documentation systems included in FastAPI.
To see the documentation, just add /docs
to the url ([**http://127.0.0.1:8000/docs**](http://127.0.0.1:8000/docs)
). This link will show automatic interactive API documentation.
Click on the try it out button and you should be prompted with something like this:
Go ahead and choose a file from your computer and execute it. Essentially this is what you expect to see:
The main drawback of handling file uploads as bytes and then decoding them into a string, as shown in the provided code snippet, is that it may not be suitable for large files. When you decode a large file from bytes to a string, it can consume a significant amount of memory, potentially leading to memory issues, especially in applications where multiple large files are uploaded simultaneously.
Option Two
@app.post('/upload')
def upload_file(uploaded_file: UploadFile = File(...)):
path = f"files/{uploaded_file.filename}"
with open(path, 'w+b') as file:
shutil.copyfileobj(uploaded_file.file, file)
return {
'file': uploaded_file.filename,
'content': uploaded_file.content_type,
'path': path,
}
The code above, does the following:
@app.post('/upload'):
This line is a FastAPI decorator indicating that the following function will handle POST requests to the ‘/upload’ endpoint.def upload_file(uploaded_file: UploadFile = File(...)):
This defines a function namedupload_file
that takes a single parameteruploaded_file
.uploaded_file: UploadFile
specifies that theuploaded_file
parameter is expected to be of typeUploadFile
. FastAPI'sUploadFile
class is used to handle uploaded files.path = f"files/{uploaded_file.filename}"
This line constructs the path where the uploaded file will be saved. It uses an f-string to create a path in the ‘files’ directory with the original filename of the uploaded file.with open(path, 'w+b') as file:
This line opens a new file with write and binary mode (‘w+b’) at the specifiedpath
.'w+b'
mode ensures that the file is opened in binary mode, suitable for writing binary data.shutil.copyfileobj(uploaded_file.file, file)
This line uses theshutil.copyfileobj()
function to copy the contents of the uploaded file (uploaded_file.file
) to the newly created file (file
). It efficiently copies the binary data from the uploaded file to the local file on the server.shutil.copyfileobj(uploaded_file.file, file)
This line uses theshutil.copyfileobj()
function to copy the contents of the uploaded file (uploaded_file.file
) to the newly created file (file
).- It efficiently copies the binary data from the uploaded file to the local file on the server.
When you go back to the docs, a new endpoint should be displayed and ready for testing.
If the configuration is correct, you should be able to upload a file from your system, and the uploaded file will be stored inside your project’s directory, similar to the following example:
Accessing Static Files
In most cases, serving static files is a common use case in most web applications and FastApi
provides a really simple way to make files statically available. Here is how you can achieve this:
Importing the necessary module
from fastapi.staticfiles import StaticFiles
Mount static files directory
app.mount('/files', StaticFiles(directory='files'),'files')
Testing out
It works!!!
File Integrity: Validating File Types Based on Content
Content-based file validation is a crucial aspect of handling file uploads in web applications. Unlike relying solely on file extensions, content-based validation ensures that uploaded files are of the expected type by examining their actual content. This approach adds an extra layer of security and accuracy, preventing malicious uploads and enhancing user experience.
For this example, we will validate that the only file we can be able to upload is that of a valid JSON file.
import shutil
import json
from fastapi import FastAPI, UploadFile, File, status
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import HTTPException
@app.post('/upload/file')
def upload_json(file:UploadFile = File(...)):
if file.content_type != 'application/json':
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Wow, That's not allowed")
data = json.loads(file.file.read())
return {
"filename":file.filename,
"content":data
}
Breakdown of the code above:
@app.post('/upload/file'):
This line is a FastAPI decorator indicating that the following function will handle POST requests to the ‘/upload/file’ endpoint.def upload_json(file: UploadFile = File(...)):
- This defines a function named
upload_json
that takes a single parameterfile
. file: UploadFile = File(...)
specifies that thefile
parameter is expected to be of typeUploadFile
, which is used to handle uploaded files= File(...)
indicates that FastAPI will automatically parse the incoming request and extract the file data. TheFile(...)
dependency is used for handling file uploads in FastAPI.- Content Type Validation:
if file.content_type != 'application/json':
This line checks if the uploaded file’s content type is not ‘application/json’. If the uploaded file is not a JSON file, it raises an HTTPException with a 400 Bad Request status code and a custom error message: “Wow, That’s not allowed”. - Loading JSON Data:
data = json.loads(file.file.read())
- If the uploaded file is determined to be a JSON file, this line reads the file content and loads it as a JSON object using
json.loads()
.file.file.read()
reads the binary data of the uploaded file.json.loads()
parses the JSON data into a Python dictionary. - Response:
return {"filename": file.filename, "content": data}
If the file is a valid JSON file, the function returns a dictionary containing the filename and the loaded JSON content. This response will be sent back to the client.
Testing out
That works as expected!!
Uploading and handling File uploads
We’ve explored methods for uploading files and ensuring their static availability. Now, let’s focus on enabling interactive downloads in specific formats. In this case, let’s examine how to facilitate the conversion from a JSON file to a YAML file.
import shutil
import json
import time
import yaml
from fastapi import FastAPI, UploadFile, File, status
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import HTTPException
import os
app = FastAPI()
time_str = time.strftime('%Y-%m%d - %H%M%S')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
UPLOAD_DIR = os.path.join(BASE_DIR, "uploads")
@app.post('/upload/download')
def upload_download(file:UploadFile = File(...)):
if file.content_type != 'application/json':
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Wow, that's not allowed")
data = json.loads(file.file.read())
new_filename = "{}_{}.yaml".format(os.path.splitext(file.filename)[0], time_str)
SAVE_F = os.path.join(UPLOAD_DIR, new_filename)
with open(SAVE_F, "w") as f:
yaml.dump(data,f)
return FileResponse(path=SAVE_F, media_type="application/octet-stream", filename=new_filename)
Break down of the code above:
- This code defines a POST request endpoint at the path ‘/upload/download’. It expects an uploaded file of type
UploadFile
. - This part checks whether the uploaded file is of JSON type. If the file’s content type is not ‘application/json’, it raises a Bad Request HTTPException with a message indicating that the uploaded file type is not allowed.
- This line reads the content of the uploaded JSON file and loads it into a Python dictionary using
json.loads()
. Thefile.file
attribute represents the file object. new_filename
is created by combining the original filename (without extension) with the timestamp generated earlier. This new filename has a '.yaml' extension.SAVE_F
represents the complete path where the converted YAML file will be saved, including the 'uploads' directory.- The code then opens the file specified by
SAVE_F
in write mode and usesyaml.dump()
to write the Python dictionary data into the YAML file. - Finally, the function returns a
FileResponse
object. It specifies the path to the saved YAML file (path=SAVE_F
), sets the media type to "application/octet-stream", indicating a binary file, and provides the desired filename for the downloaded file (filename=new_filename
).
Note: you should create a uploads directory before testing out whether it works
After an instance of running your server, and testing it out with a JSON file, this is what you should see.
Everything to the output will work as expected.
Conclusion
This article delved into the intricacies of handling file uploads and downloads in FastAPI, a modern and efficient Python web framework. Here are the key takeaways:
- File Handling: We explored different methods of handling file uploads and downloads, covering various scenarios such as validating file types, processing content-based validation, and dealing with large files.
- Content-Based Validation: Content-based validation emerged as a crucial technique, allowing developers to validate files based on their actual content rather than relying solely on file extensions. This enhances security and ensures the integrity of uploaded files.
- Integration of Python Libraries: We discussed how to integrate Python libraries like
shutil
for efficient file copying, and streamlining the file handling process. - Static File Serving: Additionally, we covered serving static files using FastAPI, enabling developers to make images, stylesheets, and scripts directly accessible to clients, enhancing the user experience.
- Robust Error Handling: Proper error handling, including HTTP status codes and meaningful error messages.
As you wrap up this insightful article, I want to share something extraordinary with you. Beyond crafting compelling content, I lead a freelance group specializing in innovative software solutions. 🌐✨
Imagine having a website that not only looks stunning but functions seamlessly, APIs that enhance your operations, and the power of data visualization and machine learning at your fingertips. That’s what we bring to the table! 💻🚀
Whether you’re a visionary entrepreneur or a growing SME, our team is here to turn your tech dreams into reality. Let’s redefine what’s possible. Connect with us on Gmail, LinkedIn, or WhatsApp, and let’s embark on a journey of digital transformation together. Your success story begins now!
Reach out to us at:
Gmail: mbs.team.general@gmail.com