Creating a Recipe Model
As you can imagine, a recipe may have several attributes. To save every detail of these attributes, we will model the recipe using a class. This recipe class is going to have several essential attributes.
Here is a brief description of the attributes that we will define in the recipe class:
- name: The name of the recipe.
- description: The description of the recipe.
- num_of_servings: The number of servings.
- cook_time: The cooking time required. This is an integer whose units are in seconds.
- directions: The directions.
- is_publish: The publish status of the recipe; the default is draft.
In the next exercise, we will show you how to code the recipe class so that it has these attributes.
Exercise 6: Creating the Recipe Model
In this exercise, we will code the recipe model, step by step. The recipe class will contain the attributes that we discussed previously. The code file for this exercise can be found in Lesson2/Exercise06/models/recipe.py.
Now, let's create the recipe class:
- Right-click on the project name, that is, Smilecook, and create a Python Package. Name it models:
Figure 2.5: Creating a Python package and naming it models
- Then, create a file called recipe.py under models and type in the following code:
recipe_list = []
def get_last_id():
if recipe_list:
last_recipe = recipe_list[-1]
else:
return 1
return last_recipe.id + 1
Let's pause for a while and examine the code here. First, we define recipe_list = [] so that we can store the recipes in the application memory. Then, we define the get_last_id function to get the ID of our last recipe. Later, when we create a new recipe, we will use this method to evaluate the last ID in recipe_list so that we can come up with a new ID for the new recipe.
- Define the recipe class using the following code. Type the following code into recipe.py, right after the get_last_id function that we implemented:
class Recipe:
def __init__(self, name, description, num_of_servings, cook_time, directions):
self.id = get_last_id()
self.name = name
self.description = description
self.num_of_servings = num_of_servings
self.cook_time = cook_time
self.directions = directions
self.is_publish = False
The Recipe class has the __init__ constructor method, which will take in parameters such as name, description, num_of_servings, cook_time, and directions, and create the recipe object based on that. The ID is self-incremented and is_publish is set to false by default. This means that, by default, the recipe will be set to draft (not published).
- In the same Recipe class, define the data method for returning the data as a dictionary object. You will recall that, in Python, indentation matters. The following code is indented since it is under the Recipe class:
@property
def data(self):
return {
'id': self.id,
'name': self.name,
'description': self.description,
'num_of_servings': self.num_of_servings,
'cook_time': self.cook_time,
'directions': self.directions
}
Now that we have built the recipe model, we will go ahead and build the API endpoint using Flask-RESTful.
Resourceful Routing
The main building blocks in Flask-RESTful are resources. Resources are built on top of Flask's pluggable view. The concept of resourceful routing is that we want to structure all the client requests around resources. In our recipe-sharing platform, we are going to group the CRUD actions on a recipe under RecipeResource. For publish and unpublish actions, we will group them under a different RecipePublishResource. This provides a clear structure that other developers can follow.
The way in which we can implement these resources is simple: we just need to inherit from the flask_restful.Resource class and implement the methods that correspond to the HTTP verb inside it.
In the next exercise, we will define three subclasses: one for the collection of recipes, one for a single recipe, and one for publishing the recipe.
Exercise 7: Defining an API Endpoint for the Recipe Model
To build an API endpoint, we need to define a class that inherits from flask_restful.Resource. Then, we can declare the get and post methods inside the class. Let's get started:
- Create a folder called resources under the project and then create a file called recipe.py under the resources folder.
Note
The code file for this can be found in the https://github.com/TrainingByPackt/Python-API-Development-Fundamentals/tree/master/Lesson02/Exercise07/resources.
- Import the necessary packages, classes, and functions using the following code:
from flask import request
from flask_restful import Resource
from http import HTTPStatus
from models.recipe import Recipe, recipe_list
- Right after the preceding code import, create the RecipeListResource class. This class has GET and POST methods, which are used to get and create the recipe's resources, respectively. We will finish the get method first:
class RecipeListResource(Resource):
def get(self):
data = []
for recipe in recipe_list:
if recipe.is_publish is True:
data.append(recipe.data)
return {'data': data}, HTTPStatus.OK
Here, we have created and implemented the RecipeListResource class, which inherits from flask-restful.Resource. The get method that we implemented is for, getting all the public recipes back. It does this by declaring a data list and getting all the recipes with is_publish = true in recipe_list. These recipes are appended to our data list and returned to the users.
- Add the post method. This is used to create the recipe:
def post(self):
data = request.get_json()
recipe = Recipe(name=data['name'],
description=data['description'],
num_of_servings=data['num_of_servings'],
cook_time=data['cook_time'],
directions=data['directions'])
recipe_list.append(recipe)
return recipe.data, HTTPStatus.CREATED
In this exercise, we have built two methods that handle the GET and POST client requests. The following table summarizes the methods that we have built in this exercise:
Figure 2.6: Client request methods that we used in this exercise
Note
We have skipped the step to jsonify the object before returning data to the client because Flask-RESTful has already done that for us behind the scenes.
The post method that we built in this exercise is for creating a new recipe. It is a POST method. It does this by getting the JSON data back from the request using request.get_json and then creates the recipe object and stores that in recipe_list. Finally, it returns the recipe record with an HTTP status code 201 CREATED.
Exercise 8: Defining the Recipe Resource
In this exercise, we will define the recipe resource. We are going to use two methods: the get method, for getting back a single recipe; and the put method, for updating the recipe. Let's get started:
- Define the RecipeResource resource and implement the get method by using the following sample code:
class RecipeResource(Resource):
def get(self, recipe_id):
recipe = next((recipe for recipe in recipe_list if recipe.id == recipe_id and recipe.is_publish == True), None)
if recipe is None:
return {'message': 'recipe not found'}, HTTPStatus.NOT_FOUND
return recipe.data, HTTPStatus.OK
Similarly, RecipeResource also inherits from flask-restful.Resource. The get method we are implementing here is getting back a single recipe. We do that by searching for recipe_id in recipe_list. We will only get back those recipes with is_publish = true. If no such recipe is found, we will return the message recipe not found. Otherwise, we will return the recipe, along with an HTTP status of 200 OK.
- Implement the put method with the following code:
def put(self, recipe_id):
data = request.get_json()
recipe = next((recipe for recipe in recipe_list if recipe.id == recipe_id), None)
if recipe is None:
return {'message': 'recipe not found'}, HTTPStatus.NOT_FOUND
recipe.name = data['name']
recipe.description = data['description']
recipe.num_of_servings = data['num_of_servings']
recipe.cook_time = data['cook_time']
recipe.directions = data['directions']
return recipe.data, HTTPStatus.OK
The second method we've implemented here is put. It gets the recipe details from the client request using request.get_json and updates the recipe object. Then, it returns the HTTP status code 200 OK if everything goes well.
Here, we have built two methods for the recipe resources. The GET and PUT methods are used to handle the corresponding client request. The following table shows the methods that we have built for the RecipeResource class in this exercise:
Figure 2.7: Methods that we have built for the RecipeResource class
Exercise 9: Publishing and Unpublishing the Recipes
In the previous exercises, we created the recipe resources and their associated methods. Now, our Smilecook application can read/write actions on recipes. However, at the beginning of this chapter, we said that the recipes can have two Statuses (unpublished and published). This allows the user to continue updating their unpublished recipes before publishing them to the world. In this exercise, we will define the resource for publishing and unpublishing a recipe. Let's get started:
- Define the RecipePublic resource and implement the put method that will handle the HTTP PUT request:
class RecipePublishResource(Resource):
def put(self, recipe_id):
recipe = next((recipe for recipe in recipe_list if recipe.id == recipe_id), None)
if recipe is None:
return {'message': 'recipe not found'}, HTTPStatus.NOT_FOUND
recipe.is_publish = True
return {}, HTTPStatus.NO_CONTENT
RecipePublishResource inherits from flask_restful.Resource. The put method will locate the recipe with the passed-in recipe_id and update the is_publish status to true. Then, it will return HTTPStatus.NO_CONTENT, which shows us that the recipe has been published successfully.
- Implement the delete method, which will handle the HTTP DELETE request:
def delete(self, recipe_id):
recipe = next((recipe for recipe in recipe_list if recipe.id == recipe_id), None)
if recipe is None:
return {'message': 'recipe not found'}, HTTPStatus.NOT_FOUND
recipe.is_publish = False
return {}, HTTPStatus.NO_CONTENT
The delete method is the opposite of the put method. Instead of setting is_publish to true, it sets it to false in order to unpublish the recipe.
You can also see that we are using these methods in a flexible manner; the put method is not necessarily for update, and the delete method is not necessarily for removal.
The following table shows all the methods that we have created in this exercise. Now that we have all three resources ready (RecipeListResource, RecipeResource, and RecipePublishResource), we will discuss endpoint configuration:
Figure 2.8: Methods that we used in this exercise
Note
If the client request is with an HTTP verb that has no corresponding handling method in the resource, Flask-RESTful will return the HTTP status code 405 Method Not Allowed.