Python

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.

Fig. 1: Application structure

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 the uploads 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.

Fig. 2: Application demo

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.

Fig. 3: Uploaded file

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.

Fig. 4: Viewing the files and downloading

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.

Download
You can download the full source code of this example here: Download File Using Python Flask

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button