Our database has a few rows for each of the tables that persist the models we have defined. However, after we start working with our API in a real-life production environment, we will have hundreds of messages, and therefore, we will have to deal with large result sets. Thus, we will create a generic pagination class and we will use it to easily specify how we want large results sets to be split into individual pages of data.
First, we will compose and send HTTP requests to create 9
messages that belong to one of the categories we have created: Information
. This way, we will have a total of 12 messages persisted in the database. We had 3 messages and we add 9 more.
http POST :5000/api/messages/ message='Initializing light controller' duration=25 category="Information" http POST :5000/api/messages/ message='Initializing light sensor' duration=20 category="Information" http POST :5000/api/messages/ message='Checking pressure sensor' duration=18 category="Information" http POST :5000/api/messages/ message='Checking gas sensor' duration=14 category="Information" http POST :5000/api/messages/ message='Setting ADC resolution' duration=22 category="Information" http POST :5000/api/messages/ message='Setting sample rate' duration=15 category="Information" http POST :5000/api/messages/ message='Initializing pressure sensor' duration=18 category="Information" http POST :5000/api/messages/ message='Initializing gas sensor' duration=16 category="Information" http POST :5000/api/messages/ message='Initializing proximity sensor' duration=5 category="Information"
The following are the equivalent curl
commands:
curl -iX POST -H "Content-Type: application/json" -d '{"message":" Initializing light controller", "duration":25, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Initializing light sensor", "duration":20, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Checking pressure sensor", "duration":18, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Checking gas sensor", "duration":14, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Setting ADC resolution", "duration":22, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Setting sample rate", "duration":15, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Initializing pressure sensor", "duration":18, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Initializing gas sensor", "duration":16, "category": "Information"}' :5000/api/messages/ curl -iX POST -H "Content-Type: application/json" -d '{"message":"Initializing proximity sensor", "duration":5, "category": "Information"}' :5000/api/messages/
The previous commands will compose and send nine POST
HTTP requests with the specified JSON key-value pairs. The request specifies /api/messages/
, and therefore, it will match '/messages/'
and run the MessageListResource.post
method, that is, the post
method for the MessageListResource
class.
Now, we have 12 messages in our database. However, we don't want to retrieve the 12 messages when we compose and send a GET
HTTP request to /api/messages/
. We will create a customizable generic pagination class to include a maximum of 5 resources in each individual page of data.
Open the api/config.py
file and add the following lines that declare two variables that configure the global pagination settings. The code file for the sample is included in the restful_python_chapter_07_01
folder:
PAGINATION_PAGE_SIZE = 5 PAGINATION_PAGE_ARGUMENT_NAME = 'page'
The value for the PAGINATION_PAGE_SIZE
variable specifies a global setting with the default value for the page size, also known as limit. The value for the PAGINATION_PAGE_ARGUMENT_NAME
specifies a global setting with the default value for the argument name that we will use in our requests to specify the page number we want to retrieve.
Create a new helpers.py
file within the api
folder. The following lines show the code that creates a new PaginationHelper
class. The code file for the sample is included in the restful_python_chapter_07_01
folder:
from flask import url_for from flask import current_app class PaginationHelper(): def __init__(self, request, query, resource_for_url, key_name, schema): self.request = request self.query = query self.resource_for_url = resource_for_url self.key_name = key_name self.schema = schema self.results_per_page = current_app.config['PAGINATION_PAGE_SIZE'] self.page_argument_name = current_app.config['PAGINATION_PAGE_ARGUMENT_NAME'] def paginate_query(self): # If no page number is specified, we assume the request wants page #1 page_number = self.request.args.get(self.page_argument_name, 1, type=int) paginated_objects = self.query.paginate( page_number, per_page=self.results_per_page, error_out=False) objects = paginated_objects.items if paginated_objects.has_prev: previous_page_url = url_for( self.resource_for_url, page=page_number-1, _external=True) else: previous_page_url = None if paginated_objects.has_next: next_page_url = url_for( self.resource_for_url, page=page_number+1, _external=True) else: next_page_url = None dumped_objects = self.schema.dump(objects, many=True).data return ({ self.key_name: dumped_objects, 'previous': previous_page_url, 'next': next_page_url, 'count': paginated_objects.total })
The PaginationHelper
class declares a constructor, that is, the __init__
method that received many arguments and uses them to initialize the attributes with the same names:
request
: The Flask request object that will allow the paginate_query
method to retrieve the page number value specified with the HTTP request.query
: The SQLAlchemy query that the paginate_query
method has to paginate.resource_for_url
: A string with the resource name that the paginate_query
method will use to generate the full URLs for the previous page and the next page.key_name
: A string with the key name that the paginate_query
method will use to return the serialized objects.schema
: The Flask-Marshmallow Schema
subclass that the paginate_query
method must use to serialize the objects.In addition, the constructor reads and saves the values for the configuration variables we added to the config.py
file in the results_per_page
and page_argument_name
attributes.
The class declares the paginate_query
method. First, the code retrieves the page number specified in the request and saves it in the page_number
variable. In case no page number is specified, the code assumes that request requires the first page. Then, the code calls the self.query.paginate
method to retrieve the page number specified by page_number
of the paginated result of objects from the database, with a number of results per page indicated by the value of the self.results_per_page
attribute. The next line saves the paginated items from the paginated_object.items
attribute in the objects
variable.
If the value for the paginated_objects.has_prev
attribute is True
, it means that there is a previous page available. In this case, the code calls the flask.url_for
function to generate the full URL for the previous page with the value of the self.resource_for_url
attribute. The _external
argument is set to True
because we want to provide the full URL.
If the value for the paginated_objects.has_next
attribute is True
, it means that there is a next page available. In this case, the code calls the flask.url_for
function to generate the full URL for the next page with the value of the self.resource_for_url
attribute.
Then, the code calls the self.schema.dump
method to serialize the partial results previously saved in the object
variable, with the many
argument set to True
. The dumped_objects
variable saves the reference to the data
attribute of the results returned by the call to the dump
method.
Finally, the method returns a dictionary with the following key-value pairs:
self.key_name
: The serialized partial results saved in the dumped_objects
variable.'previous'
: The full URL for the previous page saved in the previous_page_url
variable.'previous'
: The full URL for the next page saved in the next_page_url
variable.'count'
: The total number of objects available in the complete resultset retrieved from the paginated_objects.total
attribute.18.223.171.51