Download File Using Python Flask
Hello in this tutorial, we will create a file download functionality through python flask and host it on Docker in a containerized environment.
1. Introduction
A common feature of any web application is to provide support to the file upload and download utility. In python programming, flask provides this functionality where all we need is an HTML form with the encryption level as multipart/form-data
. The server-side script will fetch the file from the requests.files[]
object and on successful uploading, it will be saved in the desired folder on the server. Similarly, we can have the download functionality to download any file (already present in the upload folder).
1.1 Setting up Python
If someone needs to go through the Python installation on Windows, please watch this link. You can download the Python from this link.
1.2 What is Docker & Setting up Docker
In the present world, Docker is an important term,
- Often used in CI/CD platform that packages and runs the application with its dependencies inside a container
- Is a standard for Linux Containers
- A Container is a runtime that runs under any Linux kernel and provides a private machine-like space under Linux
If someone needs to go through the Docker installation, please watch this video.
2. How To Download File Using Python Flask?
I am using JetBrains PyCharm as my preferred IDE. You’re free to choose the IDE of your choice. Fig. 1 represents the project structure for this tutorial.
2.1 Creating a requirements file
Add the below code to the requirements file. The file will be responsible to download and install the packages required for this tutorial.
requirements.txt
Flask Werkzeug
2.2 Basic file upload from
Define an HTML form in the templates
folder with a file
field in it. The page will also show the success or error messages from the python flask backend.
upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>File upload</title> </head> <body> <h1>Python flask file upload</h1> <hr> <p> {% with messages = get_flashed_messages() %} {% if messages %} <ul class=flashes> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} </p> <h3>Select a file to upload</h3> <form action="/" method="POST" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" value="Submit" /> </form> </body> </html>
2.3 Files list and download form
Define an HTML form in the templates
folder. The page will list the already uploaded file and a link to download the file from the server.
list.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Files</title> </head> <body> <h1>Download upload files</h1> <hr> <ul> {% for file in files %} <li><a href="{{ url_for('download', filename=file) }}">{{ file }}</a></li> {% endfor %} </ul> </body> </html>
2.4 Python code
The python file will expose four different endpoints responsible for interacting with the frontend pages.
/
endpoint – Responsible for showing the HTML page to the user via the HTTP GET call. The same endpoint will also be responsible to handle the HTTP POST call from the form and save the uploaded file in theuploads
folder/files
endpoint – Responsible to list the already uploaded files and handle the download functionality/download/<path:filename>
endpoint – Responisble for downloading the file as an attachment in the browser
main.py
import logging import os.path from flask import Flask, render_template, request, redirect, flash, send_from_directory from werkzeug.utils import secure_filename # [logging config logging.basicConfig(format='%(asctime)s:%(levelname)s:%(filename)s:%(funcName)s:%(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO) # logging config] app = Flask(__name__) app.secret_key = "somesecretkey" app.config['ALLOWED_EXTENSIONS'] = ['.jpg', '.png'] app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024 UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads') # http://localhost:5000 @app.route('/', methods=['GET']) def index(): logging.info('Showing index page') return render_template('upload.html') @app.route('/', methods=['POST']) def upload_files(): """Upload a file.""" logging.info('Starting file upload') if 'file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['file'] # obtaining the name of the destination file filename = file.filename if filename == '': logging.info('Invalid file') flash('No file selected for uploading') return redirect(request.url) else: logging.info('Selected file is= [%s]', filename) file_ext = os.path.splitext(filename)[1] if file_ext in app.config['ALLOWED_EXTENSIONS']: secure_fname = secure_filename(filename) file.save(os.path.join(UPLOAD_FOLDER, secure_fname)) logging.info('Upload is successful') flash('File uploaded successfully') return redirect('/') else: logging.info('Invalid file extension') flash('Not allowed file type') return redirect(request.url) @app.route('/download/<path:filename>', methods=['GET']) def download(filename): """Download a file.""" logging.info('Downloading file= [%s]', filename) logging.info(app.root_path) full_path = os.path.join(app.root_path, UPLOAD_FOLDER) logging.info(full_path) return send_from_directory(full_path, filename, as_attachment=True) # http://localhost:5000/files @app.route('/files', methods=['GET']) def list_files(): """Endpoint to list files.""" logging.info('Listing already uploaded files from the upload folder.') upf = [] for filename in os.listdir(UPLOAD_FOLDER): path = os.path.join(UPLOAD_FOLDER, filename) if os.path.isfile(path): upf.append(filename) # return jsonify(uploaded_files) return render_template('list.html', files=upf) def check_upload_dir(): if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER, exist_ok=True) if __name__ == '__main__': check_upload_dir() # Development only: run "python app.py" and open http://localhost:5000 server_port = os.environ.get('PORT', '5000') app.run(debug=False, port=server_port, host='0.0.0.0')
For localhost debugging, you can run the application by running the main.py
py file in the PyCharm IDE. The application will be started on the 5000
port number and will expose the /
and /files
endpoint for playing with the application.
3. Setting up requirements for Docker
To deploy this application on Docker we will create two files responsible for having this application up and running in a container. We will also use the .dockerignore
file so that we only add the required files to the docker image and the same file can be downloaded from the Downloads section.
3.1 Creating a Dockerfile
Add the below code to the Dockerfile responsible for creating the Docker image. The image name will be driven from the docker-compose.yml
file which will be created in the next step.
Dockerfile
# Python image to use FROM python:3.10-slim # Author of the image MAINTAINER danielatlas # Set the working directory to /app WORKDIR /app # Copy the requirements file used for dependencies COPY requirements.txt . # Install the needed packages specified in requirements.txt RUN pip install --no-cache-dir -r requirements.txt # Copy rest of the working directory contents into the container at /app COPY . . # Run app.py when the container launches ENTRYPOINT ["python3", "main.py"]
3.2 Creating the docker-compose yml
Add the below code to the docker-compose file responsible for bundling the code, creating the docker image, and starting the container on a port number – 5000
from the created image. You are free to change the details as per your wish but remember that if you change the port number hit the application endpoint on the same port. For this tutorial, I am setting the port number to 5000
.
docker-compose.yml
version: "3.7" services: pythonapp: build: context: . dockerfile: Dockerfile image: "pythonapp:latest" container_name: "pythonapp" ports: - "5000:5000"
4. Code run & demo
To run the application on the docker container follow the below steps. Open the terminal and navigate to the project directory containing the Dockerfile.
App run
-- to create the image and start container -- docker-compose up -d --build -- to check if a container is started docker ps -a -- to stop and remove the container -- docker-compose down -- to view the container logs -- docker logs --follow pythonapp -- to remove the created image -- docker rmi pythonapp
Once the up command is successful you can hit the following endpoint – http://localhost:5000
in the browser of your choice and the below page will appear.
An HTML form like in Fig. 2 will be shown to the users and the user can browse the file system for the file and will be uploaded to the server inside the uploads
folder. Once the file is uploaded successfully it will be shown in the directory.
Once the file is uploaded successfully let us verify the same. For verification you can hit the following endpoint – http://localhost:5000/files
. All the uploaded files will be shown below and you can click on the filename to download it.
That is all for this tutorial and I hope the article served you with whatever you were looking for. Happy Learning and do not forget to share!
5. Summary
In this tutorial, we learned about the file upload functionality in a python application using the flask module and hosted the same app on docker. You can download the source code of this tutorial from the Downloads section.
6. Download the Project
This was a tutorial on how to create a python flask file upload functionality and run it in the localhost and dockerized environment.
You can download the full source code of this example here: Download File Using Python Flask