Welcome to the fourth lesson of our course on building an image generation service with Flask! So far, we've built several key components of our application: the PromptManager
for formatting user inputs, the ImageManager
for storing and processing images, and the ImageGeneratorService
that connects to Google's Gemini API to generate images.
Now, we're ready to add another important layer to our application architecture: the controller. In web applications, controllers play a crucial role in the Model-View-Controller (MVC) pattern. They act as intermediaries between the service layer (which contains our business logic) and the presentation layer (which handles user interactions).
The controller's primary responsibilities include:
- Receiving and validating input from the user interface
- Calling the appropriate service methods with validated inputs
- Handling errors that might occur during processing
- Formatting responses in a consistent way before sending them back to the user
In our image generation application, the controller will receive text prompts from users, validate them, pass them to our ImageGeneratorService
, and then format the responses (either successful image data or error messages) before sending them back.
By adding this controller layer, we're further improving the separation of concerns in our application. The service layer can focus purely on business logic (generating images), while the controller handles the specifics of HTTP requests and responses. This makes our code more maintainable, testable, and easier to extend in the future.
Let's start by creating our controller class. We'll create a new file called image_generator_controller.py
in the app/controllers
directory. This controller will depend on our ImageGeneratorService
to perform the actual image generation.
Here's how we'll set up the basic structure of our controller:
Python1from flask import jsonify 2from services.image_generator_service import ImageGeneratorService 3 4class ImageGeneratorController: 5 def __init__(self): 6 self.image_generator_service = ImageGeneratorService()
In this code, we're importing the jsonify
function from Flask, which will help us format our responses as JSON. We're also importing our ImageGeneratorService
class that we created in the previous lesson.
The controller's constructor initializes an instance of the ImageGeneratorService
. This establishes the dependency between our controller and service layers. By creating this dependency in the constructor, we're following the principle of dependency injection, which makes our code more modular and easier to test.
Our controller will be responsible for two main operations:
- Generating a new image based on user input
- Retrieving all previously generated images
For each of these operations, we'll create a dedicated method in our controller class. These methods will handle input validation, error handling, and response formatting, ensuring that our API provides a consistent interface to clients.
Now, let's implement the method for generating images. This method will receive user input, validate it, call the service to generate an image, and format the response:
Python1def generate_image(self, user_input): 2 if not user_input: 3 return {'error': 'Missing input'}, 400 4 try: 5 base64_image = self.image_generator_service.generate_image(user_input) 6 return {'image': base64_image} 7 except Exception as e: 8 return {'error': str(e)}, 500
Let's break down what's happening in this method:
First, we check if the user_input
is empty or None
. If it is, we return an error response with a 400 status code (Bad Request), indicating that the client provided invalid input. The response is a dictionary with an 'error'
key containing a descriptive message.
If the input is valid, we enter a try-except block. Within the try block, we call the generate_image
method of our service, passing the user input. This method will format the prompt, call the Gemini API, and return the base64-encoded image data.
If the image generation is successful, we return a dictionary with an 'image'
key containing the base64 string. This response will have a default status code of 200 (OK).
If an exception occurs during image generation, we catch it in the except block and return an error response with a 500 status code (Internal Server Error). The response includes the error message from the exception.
Notice how we're using HTTP status codes to indicate the nature of the response. This is a standard practice in RESTful APIs:
- 200: Success
- 400: Client error (invalid input)
- 500: Server error (something went wrong on our end)
By returning both a response body and a status code, we provide clients with all the information they need to handle the response appropriately.
Next, let's implement the method for retrieving all previously generated images:
Python1def get_images(self): 2 images = self.image_generator_service.get_all_images() 3 return jsonify({'images': images})
This method is simpler than the previous one because it doesn't require input validation. We simply call the get_all_images
method of our service, which returns a list of all stored images.
We then use Flask's jsonify
function to convert the list of images into a JSON response. The jsonify
function not only converts Python objects to JSON but also sets the appropriate content type headers in the HTTP response.
The response is a dictionary with an 'images'
key containing the list of image objects. Each image object includes an ID, the prompt used to generate it, and the base64-encoded image data.
Unlike the generate_image
method, we don't need to handle exceptions here because the get_all_images
method doesn't involve external API calls or complex processing that might fail. However, in a more robust implementation, you might still want to add error handling for unexpected scenarios.
With these two methods, our controller provides a complete interface for clients to generate images and retrieve previously generated ones. The controller abstracts away the details of how images are generated and stored, presenting a clean, consistent API to clients.
Now that we've implemented our controller, let's validate it to make sure it works correctly. We'll update our app/main.py
file to use the new controller:
Python1from controllers.image_generator_controller import ImageGeneratorController 2 3controller = ImageGeneratorController() 4 5# Simulate image generation 6try: 7 response = controller.generate_image("Luxury Tech Conference 2025") 8 if 'error' in response: 9 print(f"Error: {response['error']}") 10 else: 11 print("Generated Image:") 12 print(response['image']) 13except Exception as e: 14 print(f"Error generating image: {str(e)}") 15 16# Retrieve all images through the controller 17all_images_response = controller.get_images() 18print("\nAll Images via Controller:") 19print(all_images_response.json)
In this test script, we're creating an instance of our ImageGeneratorController
and using it to perform two operations:
First, we call the generate_image
method with a sample prompt. We check if the response contains an 'error'
key, which would indicate that something went wrong. If there's no error, we print the base64-encoded image data.
Then, we call the get_images
method to retrieve all stored images. We print the JSON representation of the response, which includes all previously generated images.
When running this script with a valid API key, you would see output similar to:
1Generated Image: 2/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a... 3 4All Images via Controller: 5{'images': [{'id': 0, 'prompt': 'You are the lead graphic designer for 'Eventify Co.'...', 'image_base64': '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a...'}]}
The base64 string would be much longer in a real application, but it's truncated here for readability. This string represents the encoded image data that can be used directly in an HTML <img>
tag.
Let's also test the error handling by trying to generate an image with an empty prompt:
Python1# Test error handling with empty input 2empty_response = controller.generate_image("") 3print("\nEmpty Input Test:") 4print(f"Status Code: {empty_response[1]}") 5print(f"Response: {empty_response[0]}")
This would produce output like:
1Empty Input Test: 2Status Code: 400 3Response: {'error': 'Missing input'}
This confirms that our controller correctly validates input and returns appropriate error responses with status codes.
In this lesson, we've built the ImageGeneratorController
, which serves as an intermediary between our service layer and the future Flask routes. This controller handles input validation, error management, and response formatting, providing a clean, consistent interface for clients to interact with our image generation service.
Let's review what we've learned:
- We understood the role of controllers in the MVC architecture and how they fit into our application.
- We created a controller class that depends on our
ImageGeneratorService
. - We implemented methods for generating images and retrieving previously generated ones.
- We added input validation and error handling to ensure robust operation.
- We tested our controller to verify that it works correctly.
The ImageGeneratorController
is a crucial component of our application architecture. It abstracts away the details of how images are generated and stored, presenting a clean, consistent API to clients. This separation of concerns makes our code more maintainable, testable, and easier to extend.
In the next lesson, we'll integrate our controller with Flask routes to create a complete web API for image generation. We'll define endpoints that clients can call to generate images and retrieve previously generated ones.
In the upcoming practice exercises, you'll have the opportunity to work with the ImageGeneratorController
, testing its functionality with different inputs and exploring how it handles various scenarios. You'll also get to experiment with error handling and see how the controller formats responses for different types of requests.
With the ImageGeneratorController
in place, we're one step closer to having a complete image generation web application. In the next lesson, we'll see how to expose our controller's functionality through HTTP endpoints using Flask.
