AI-Assisted Development
Development Phase Backend
Testing with Postman
In this guide, we walk you through testing the image upload endpoint of our Flask API. The endpoint now features enhanced image-loading, validation, robust error handling, and detailed logging. We use Postman Essentials to simulate various scenarios—including successful uploads and error conditions—to ensure the system behaves as expected.
Below is an excerpt of the image upload endpoint. This snippet demonstrates how we manage file selection, validate file extensions, check file content using both imghdr and Pillow, and verify the quality parameter. Notice that after reading the file content, we reset the file pointer to ensure proper subsequent processing.
def upload():
image = request.files['image']
if image.filename == '':
logger.error('No image selected for uploading')
return jsonify({'error': 'No image selected for uploading'}), 400
# Secure the filename
filename = secure_filename(image.filename)
# Check the file extension
allowed_extensions = {'png', 'jpg', 'jpeg', 'gif'}
if not ('.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions):
logger.error('Invalid file extension: %s', filename)
return jsonify({'error': 'Invalid file extension'}), 400
# Check the file content
image_content = image.read()
image.seek(0) # Reset the file pointer to the beginning
if imghdr.what(None, h=image_content) not in allowed_extensions:
logger.error('Invalid image file content for: %s', filename)
return jsonify({'error': 'Invalid image file'}), 400
# Additional validation using Pillow
try:
img = Image.open(io.BytesIO(image_content))
Running the Flask Server and Testing with Postman
Once the Flask server is running using flask run
, open Postman Essentials and follow these steps:
- Select an Image: For this test, we use an image of the Northern Lights.
- Set the Parameters: In Postman, create a new request with the following details.
Below is an example request for a full-quality image upload:
POST http://127.0.0.1:5000/upload
Key Value
image DSC90804.JPG
quality 100
This test confirms that the system only accepts specific file types—PNG, JPEG, JPG, and GIF—even when JPEG is a less common file extension.
Additional Upload Function Verification
The following snippet shows an alternative version of the upload function. This version first checks if the 'image' key exists, then validates the file extension and content:
def upload():
try:
if 'image' not in request.files:
logger.error('No image part in the request')
return jsonify({'error': 'No image part in the request'}), 400
image = request.files['image']
if image.filename == '':
logger.error('No image selected for uploading')
return jsonify({'error': 'No image selected for uploading'}), 400
# Secure the filename
filename = secure_filename(image.filename)
# Check the file extension
allowed_extensions = {'png', 'jpg', 'jpeg', 'gif'}
if not ('.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions):
logger.error('Invalid file extension: %s', filename)
return jsonify({'error': 'Invalid file extension'}), 400
# Check the file content
image_content = image.read()
image.seek(0) # Reset the file pointer to the beginning
if imghdr.what(None, h=image_content) not in allowed_extensions:
logger.error('Invalid image file content for: %s', filename)
return jsonify({'error': 'Invalid image file'}), 400
Testing with a JPEG File
For a JPEG file upload, the request might look like this:
POST http://127.0.0.1:5000/upload
Key Value
image DSC08804.JPG
quality 100
Testing with a PNG File
After selecting a PNG image (e.g., book.png
), setting the quality parameter to 100 should display an acceptable image. However, reducing quality to 5 will result in poor output:
POST http://127.0.0.1:5000/upload
Params:
image: File --> book.png
quality: Text --> 5
Testing with Another JPEG File
Similarly, testing with another JPEG file (coolgirl.jpeg
) confirms that the quality adjustments work as expected:
POST http://127.0.0.1:5000/upload
Key Value
image File: coolgirl.jpeg
quality Text: 100
Response:
200 OK
Testing with a GIF File
When attempting to upload a GIF file, you may see a "failed to decode image" error. This outcome is expected because OpenCV's imdecode function does not support GIF images:
def upload():
logger.error('Invalid image file content')
return jsonify({'error': 'Invalid image file'})
# Additional validation using Pillow
try:
img = Image.open(io.BytesIO(image_content))
img.verify() # Verify that it is, in fact, an image
except (IOError, SyntaxError) as e:
logger.error('Invalid image file: %s', e)
return jsonify({'error': 'Invalid image file'})
# Get the quality parameter from the request
quality = request.form.get('quality', default=75)
# Validate the quality parameter
if quality < 0 or quality > 100:
logger.error('Quality must be between 0 and 100')
return jsonify({'error': 'Quality must be between 0 and 100'})
# 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:
logger.error('Failed to decode image: %s', image.filename)
return jsonify({'error': 'Failed to decode image'})
A sample console output might be:
INFO:werkzeug:127.0.0.1 - - [20/Nov/2024 20:57:51] "POST /upload HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [20/Nov/2024 20:58:32] "POST /upload HTTP/1.1" 400 -
ERROR:app.routes:Failed to decode image: [<filename>
Note
Since our application does not support GIF images, you will consistently see error messages for such files. For handling GIFs, consider using Pillow as recommended by BlackboxAI and Tabnine.
Enhanced Quality Parameter Validation
Below is an updated version of the upload function with improved quality parameter validation. This version ensures that the quality parameter is present and within the allowed range (0–100). If the parameter is omitted or invalid, an error message is returned:
# Check if quality parameter is present
if 'quality' not in request.form:
logger.error('Quality parameter is missing')
return jsonify({'error': 'Quality parameter is required'}), 400
# Get the quality parameter from the request
quality = request.form.get('quality', type=int)
# Validate the quality parameter
if quality is None or quality < 0 or quality > 100:
logger.error('Invalid quality value: %s', quality)
return jsonify({'error': 'Quality must be an integer 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:
logger.error('Failed to decode image: %s', filename)
return jsonify({'error': 'Failed to decode image'}), 400
# Process the image with OpenCV
buffer = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), quality])[1]
# Create a Bytes object from the buffer
After running Flask and testing the endpoint, if the quality parameter is missing you will receive:
{
"error": "Quality parameter is required"
}
A successful request with a valid quality value returns the processed image.
Final Version of the Flask Route
The final structure of our Flask route, incorporating all improvements, is shown below:
import logging
from flask import Blueprint, request, jsonify, send_file
import cv2
import numpy as np
import io
import imghdr
from werkzeug.utils import secure_filename
from PIL import Image
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
bp = Blueprint('main', __name__)
@bp.route('/upload', methods=['POST'])
def upload():
try:
if 'image' not in request.files:
logger.error('No image part in the request')
return jsonify({'error': 'No image part in the request'}), 400
image = request.files['image']
if image.filename == '':
logger.error('No image selected for uploading')
return jsonify({'error': 'No image selected for uploading'}), 400
# Secure the filename
filename = secure_filename(image.filename)
# Check the file extension
allowed_extensions = {'png', 'jpg', 'jpeg', 'gif'}
if not ('.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions):
logger.error('Invalid file extension: %s', filename)
return jsonify({'error': 'Invalid file extension'}), 400
# Check the file content
image_content = image.read()
image.seek(0) # Reset the file pointer to the beginning
if imghdr.what(None, h=image_content) not in allowed_extensions:
logger.error('Invalid image file content for: %s', filename)
return jsonify({'error': 'Invalid image file'}), 400
# Check if quality parameter is present
if 'quality' not in request.form:
logger.error('Quality parameter is missing')
return jsonify({'error': 'Quality parameter is required'}), 400
# Get and validate the quality parameter
quality = request.form.get('quality', type=int)
if quality is None or quality < 0 or quality > 100:
logger.error('Invalid quality value: %s', quality)
return jsonify({'error': 'Quality must be an integer between 0 and 100'}), 400
# Additional validation using Pillow
try:
img = Image.open(io.BytesIO(image_content))
img.verify() # Verify that it is an image
except (IOError, SyntaxError) as e:
logger.error('Invalid image file: %s', e)
return jsonify({'error': 'Invalid image file'}), 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:
logger.error('Failed to decode image: %s', filename)
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[1].tobytes())
# Validate the processed image
try:
processed_img = Image.open(img_io)
processed_img.verify()
except (IOError, SyntaxError) as e:
logger.error('Failed to process image: %s', e)
return jsonify({'error': 'Failed to process image'}), 400
# Reset the BytesIO pointer to the beginning
img_io.seek(0)
# Return the processed image as binary data
return send_file(img_io, mimetype='image/jpeg')
except Exception as e:
logger.exception('An unexpected error occurred: %s', e)
return jsonify({'error': 'An unexpected error occurred. Please try again later.'}), 500
After integrating these enhancements, our Flask API is robust and ready for frontend consumption. Later, we will scaffold the frontend using Cursor alongside tools like Tabnine and GitHub Copilot.
Note
When testing, check the API logs to see messages such as:
- "Quality parameter is missing"
- "Failed to decode image"
- HTTP status codes 200 or 400 depending on the test scenario.
Thank you for following along. In the next article, we will build a React application to interact with this robust API. Happy coding!
Watch Video
Watch video content