AI-Assisted Development
Development Phase Backend
Debugging Our Application
In our previous lesson, we encountered a 404 error when sending a request, and the root cause was not immediately obvious. In this guide, we'll explore classic debugging techniques enhanced with generative AI insights and refactor our application step by step for better performance and security.
Initial Application Setup
Below is the initial code for creating the Flask application instance. In this block, we load the configuration, enable debug mode, and register our routes. Notice that the configuration is printed and a confirmation is logged once the routes are imported:
from flask import Flask
def create_app():
app = Flask(__name__)
# Load configuration
app.config.from_object('app.instance.config')
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['DEBUG'] = True # Enable debug mode
# Print configuration to verify
print(f"Debug mode is {'on' if app.config['DEBUG'] else 'off'}!")
# Register routes
with app.app_context():
from . import routes
print("Routes imported successfully")
return app
When we ran the application, the terminal output looked similar to the following:
Serving Flask app 'run.py'
Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000/
127.0.0.1 - - [20/Nov/2024 15:28:33] "POST /upload HTTP/1.1" 404 -
127.0.0.1 - - [20/Nov/2024 15:34:43] "POST /upload HTTP/1.1" 404 -
127.0.0.1 - - [20/Nov/2024 16:34:42] "POST /upload HTTP/1.1" 404 -
The repeated 404 errors pointed to an issue with the /upload
route. Let’s take a closer look at that function.
The Initial Upload Route
Initially, the upload route function manually validates the quality parameter, saves the image to a local directory, processes it with OpenCV, and returns a JSON message containing the path to the processed image:
def upload():
quality = request.form.get('quality', default=10, type=int)
# Validate the quality parameter
if quality < 0 or quality > 100:
return jsonify({'error': 'Quality must be between 0 and 100'}), 400
# Save the image to a local directory
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image.filename)
image.save(image_path)
# Process the image with OpenCV
img = cv2.imread(image_path)
# Reduce quality of the image by changing the compression level
processed_image_path = os.path.join(app.config['UPLOAD_FOLDER'], 'processed_' + image.filename)
cv2.imwrite(processed_image_path, img, [int(cv2.IMWRITE_JPEG_QUALITY), quality])
# Return a success message with the path to the processed image
return jsonify({'message': 'Image successfully uploaded and processed', 'processed_image_path': processed_image_path}), 200
First Improvement: Directly Returning Processed Image
To improve performance and reduce disk I/O, we leveraged generative AI insights to modify the function. Instead of writing the image to disk, we directly read the image from the request and return it using Flask's send_file
:
@app.route('/upload', methods=['POST'])
def upload():
if 'image' not in request.files:
return jsonify({'error': 'No image part in the request'}), 400
image = request.files['image']
if image.filename == '':
return jsonify({'error': 'No image selected for uploading'}), 400
# Get the quality parameter from the request, default to 10 if not provided
quality = request.form.get('quality', default=10, type=int)
# Validate the quality parameter
if quality < 0 or quality > 100:
return jsonify({'error': 'Quality must be between 0 and 100'}), 400
# Save the image to a local directory
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image.filename)
image.save(image_path)
# Process the image with OpenCV
img = cv2.imread(image_path)
# Reduce quality of the image by changing the compression level
processed_image_path = os.path.join(app.config['UPLOAD_FOLDER'], 'processed_' + image.filename)
cv2.imwrite(processed_image_path, img, [int(cv2.IMWRITE_JPEG_QUALITY), quality])
# Return a success message with the path to the processed image
return jsonify({'message': 'Image successfully uploaded and processed', 'processed_image_path': processed_image_path})
Final Refinement: Processing Images In-Memory
In our final version, we entirely eliminate disk I/O by processing the image in memory. We use np.frombuffer
and OpenCV’s decoding and encoding functions to directly manipulate the image data, then send the processed image back as binary data:
@app.route('/upload', methods=['POST'])
def upload():
if 'image' not in request.files:
return jsonify({'error': 'No image part in the request'}), 400
image = request.files['image']
if image.filename == '':
return jsonify({'error': 'No image selected for uploading'}), 400
# Get the quality parameter from the request, default to 10 if not provided
quality = request.form.get('quality', default=10, type=int)
# Validate the quality parameter
if quality < 0 or quality > 100:
return jsonify({'error': 'Quality must be between 0 and 100'}), 400
# Read the image directly from the request into a NumPy array
img_array = np.frombuffer(image.read(), np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED)
if img is None:
return jsonify({'error': 'Failed to decode image'}), 400
# Process the image with OpenCV by encoding it with the requested JPEG quality
_, buffer = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality])
# Create a BytesIO object from the buffer
img_io = io.BytesIO(buffer)
# Return the processed image as binary data
return send_file(img_io, mimetype='image/jpeg')
Note
The in-memory processing significantly improves performance by reducing disk I/O, making the application more efficient in handling image uploads.
Adopting the Application Factory Pattern
While testing, an error was encountered indicating that the app
object was undefined in the routes.py
file. To resolve this, we refactored the application following the application factory pattern. The __init__.py
was updated as follows:
from flask import Flask
def create_app():
app = Flask(__name__)
# Load configuration
app.config.from_object('app.instance.config')
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['DEBUG'] = True # Enable debug mode
# Print configuration to verify
print(f"Debug mode is {'on' if app.config['DEBUG'] else 'off'}")
# Register routes
with app.app_context():
from . import routes
print("Routes imported successfully")
return app
Subsequently, routes.py
was updated to remove the dependency on a global app
object by using a Flask Blueprint:
from flask import Blueprint, request, jsonify, send_file
import os
import cv2
import numpy as np
from werkzeug.utils import secure_filename
import io
bp = Blueprint('main', __name__)
@bp.route('/upload', methods=['POST'])
def upload():
if 'image' not in request.files:
return jsonify({'error': 'No image part in the request'}), 400
image = request.files['image']
if image.filename == '':
return jsonify({'error': 'No image selected for uploading'}), 400
# Get the quality parameter from the request, default to 10 if not provided
quality = request.form.get('quality', default=10, type=int)
# Validate the quality parameter
if quality < 0 or quality > 100:
return jsonify({'error': 'Quality must be between 0 and 100'}), 400
# Read the image directly from the request
img_array = np.frombuffer(image.read(), np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED)
if img is None:
return jsonify({'error': 'Failed to decode image'}), 400
# Process the image with OpenCV
_, buffer = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality])
# Create a BytesIO object from the buffer
img_io = io.BytesIO(buffer)
# Return the processed image as binary data
return send_file(img_io, mimetype='image/jpeg')
Application Testing
After refactoring, the application properly initialized using the Flask application factory. The Blueprint registration ensured that the upload route was correctly integrated. Running the application with:
(venv) jeremy@Jeremys-Mac-Studio imageoptimizer.app$ flask run
* Serving Flask app "run.py"
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000/
Press CTRL+C to quit
resulted in a 200 OK response when sending a POST request to /upload
. Testing various quality parameters (e.g., quality = 10 or 100) revealed clear and noticeable differences in image compression quality.
Conclusion
This debugging session demonstrates the benefits of leveraging generative AI to propose improvements, such as eliminating unnecessary disk I/O and processing images entirely in memory. Although the process required some trial and error, the final design is robust and efficient.
Next Steps
Consider enhancing error handling and adding additional code comments in future iterations to further improve maintainability and clarity.
Happy coding, and see you in the next lesson!
Watch Video
Watch video content