pythondockersqliteflaskmakefile

Flask App running within Docker for File Upload


I wrote the following flask application which will collect a file selected and then upload it to a database running in sql lite. If i run the app locally, it works fine with the table created in the database and all the data uploaded. However if I run the app using docker, I get a 200 on file upload but do not see the table created in the database along with all the data.

Here is the code:

import os
import sqlite3
from flask import Flask, flash, jsonify, request, redirect, render_template
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.secret_key = "secret key"
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

path = os.getcwd()
uploadFolder = 'app/uploads'
if not os.path.isdir(uploadFolder):
    os.mkdir(uploadFolder)
app.config['UPLOAD_FOLDER'] = uploadFolder

allowedExtensions = set(['txt'])

# Initialize SQLite database
#database = os.path.join(path, 'file_uploads.db')
database = 'app/uploads/file_uploads.db'

# Health check to see if the service is active
@app.route('/healthCheck', methods=['GET'])
def checkStatus():
    response = {
        'healthCheck': 'Flask service is up and running!'
    }
    return jsonify(response), 200

def createTable():
    conn = sqlite3.connect(database)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS words
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                 word TEXT NOT NULL,
                 filename TEXT NOT NULL,
                 filepath TEXT NOT NULL)''')
    conn.commit()
    conn.close()

createTable()

def allowedFile(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowedExtensions

@app.route('/')
def uploadForm():
    return render_template('upload.html')

@app.route('/', methods=['POST'])
def uploadFile():
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        
        file = request.files['file']
        if file.filename == '':
            flash('No file selected for uploading')
            return redirect(request.url)
        
        if file and allowedFile(file.filename):
            filename = secure_filename(file.filename)
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            file.save(filepath)
            
            # Read content of the file and split into words
            with open(filepath, 'r') as f:
                content = f.read()
                words = content.split()
            
            # Insert each word into the SQLite database
            conn = sqlite3.connect(database)
            c = conn.cursor()
            for word in words:
                c.execute("INSERT INTO words (word, filename, filepath) VALUES (?, ?, ?)", (word, filename, filepath))
            conn.commit()
            conn.close()
            
            flash('File successfully uploaded and words saved to database')
            return render_template('upload.html')
        else:
            flash('Allowed file types are txt')
            return redirect(request.url)

# Route to get word by ID
@app.route('/word/<int:id>', methods=['GET'])
def getWordById(id):
    conn = sqlite3.connect(database)
    c = conn.cursor()
    c.execute("SELECT word FROM words WHERE id=?", (id,))
    word = c.fetchone()
    conn.close()
    if word:
        return jsonify({'id': id, 'word': word[0]}), 200
    else:
        return jsonify({'error': 'Word not found'}), 404

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

Can someone please help me figure out why the table is not visible in sql lite even though 200 appears in the log for the file upload.

Here is the expected output:

enter image description here

enter image description here

enter image description here

enter image description here

I am unable to accompolish the expected output in SQLLite when running the app using Docker. What am I doing wrong?

Here are the contents of the docker file:

# Use an official Python runtime as a parent image
FROM python:3.8-slim

# Set the working directory in the container
WORKDIR /app

# Copy the Flask application directory into the container at /app
COPY . /app

# Install any needed dependencies specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Make port 5000 available to the world outside this container
EXPOSE 5000

# Set the upload folder as a volume
VOLUME /app/uploads

# Define environment variable
ENV FLASK_APP=test.py

# Run the Flask application when the container launches
CMD ["flask", "run", "--host=0.0.0.0"]

Makefile Code:

# Variables
DOCKER_IMAGE_NAME = my-flask-app
DOCKER_CONTAINER_NAME = my-flask-container
TEST_FILE = upload/word.txt

# Targets
build:
    docker build -t $(DOCKER_IMAGE_NAME) .

run:
    docker run -d -p 5000:5000 --name $(DOCKER_CONTAINER_NAME) $(DOCKER_IMAGE_NAME)

test: build
    docker run  $(DOCKER_IMAGE_NAME) python -u test.py

upload:
    curl -X POST -F "file=@$(TEST_FILE)" http://127.0.0.1:5000/

stop:
    docker stop $(DOCKER_CONTAINER_NAME)
    docker rm $(DOCKER_CONTAINER_NAME)

.PHONY: build run stop test upload

Solution

  • I have noticed two places where your application could throw a possible error.

    First, your upload folder is not created as intended.
    You are trying to create a folder with the path app/uploads. So within your project directory, a folder app with a subfolder uploads.

    To create a complete folder structure, I recommend os.makedirs(path, exist_ok=True). This also eliminates the need to check whether the folder already exists.

    # ...
    
    uploadFolder = 'app/uploads'
    os.makedirs(uploadFolder, exist_ok=True)
    app.config['UPLOAD_FOLDER'] = uploadFolder
    
    # ...
    

    Secondly, this results in a different path specification for the volume within your docker file, since you create the directory structure app/uploads mentioned within your working directory /app.

    VOLUME /app/app/uploads
    

    With the changes mentioned, the application should run without errors.

    However, I noticed that you are storing your database in the folder where your uploads are stored. This may work in a test environment. For your productive application, I would recommend separating this. The instance path may be a sensible alternative as a storage location for the database and a subfolder for the uploads.