Now, we will create the resources that compose our main building blocks for the RESTful API. First, we will create a few instances that we will use in the different resources. Then, we will create a MessageResource
class, that we will use to represent the message resource. Create a new views.py
file within the api
folder and add the following lines. The code file for the sample is included in the restful_python_chapter_06_01
folder, as shown:
from flask import Blueprint, request, jsonify, make_response from flask_restful import Api, Resource from models import db, Category, CategorySchema, Message, MessageSchema from sqlalchemy.exc import SQLAlchemyError import status api_bp = Blueprint('api', __name__) category_schema = CategorySchema() message_schema = MessageSchema() api = Api(api_bp) class MessageResource(Resource): def get(self, id): message = Message.query.get_or_404(id) result = message_schema.dump(message).data return result def patch(self, id): message = Message.query.get_or_404(id) message_dict = request.get_json(force=True) if 'message' in message_dict: message.message = message_dict['message'] if 'duration' in message_dict: message.duration = message_dict['duration'] if 'printed_times' in message_dict: message.printed_times = message_dict['printed_times'] if 'printed_once' in message_dict: message.printed_once = message_dict['printed_once'] dumped_message, dump_errors = message_schema.dump(message) if dump_errors: return dump_errors, status.HTTP_400_BAD_REQUEST validate_errors = message_schema.validate(dumped_message) #errors = message_schema.validate(data) if validate_errors: return validate_errors, status.HTTP_400_BAD_REQUEST try: message.update() return self.get(id) except SQLAlchemyError as e: db.session.rollback() resp = jsonify({"error": str(e)}) return resp, status.HTTP_400_BAD_REQUEST def delete(self, id): message = Message.query.get_or_404(id) try: delete = message.delete(message) response = make_response() return response, status.HTTP_204_NO_CONTENT except SQLAlchemyError as e: db.session.rollback() resp = jsonify({"error": str(e)}) return resp, status.HTTP_401_UNAUTHORIZED
The first lines declare the imports and create the following instances that we will use in the different classes:
api_bp
: It is an instance of the flask.Blueprint
class that will allow us to factor the Flask application into this blueprint. The first argument specifies the URL prefix on which we want to register the blueprint: 'api'
.category_schema
: It is an instance of the CategorySchema
class we declared in the models.py
module. We will use category_schema
to validate, serialize, and deserialize categories.message_schema
: It is an instance of the MessageSchema
class we declared in the models.py
module. We will use message_schema
to validate, serialize and, deserialize categories.api
: It is an instance of the flask_restful.Api
class that represents the main entry point for the application. We pass the previously created flask.Blueprint
instance named api_bp
as an argument to link the Api
to the Blueprint
.The MessageResource
class is a subclass of flask_restful.Resource
and declares the following three methods that will be called when the HTTP method with the same name arrives as a request on the represented resource:
get
: This method receives the id of the message that has to be retrieved in the id
argument. The code calls the Message.query.get_or_404
method to return an HTTP 404 Not Found
status in case there is no message with the requested id in the underlying database. In case the message exists, the code calls the message_schema.dump
method with the retrieved message as an argument to use the MessageSchema
instance to serialize the Message
instance whose id
matches the specified id
. The dump
method takes the Message
instance and applies the field filtering and output formatting specified in the MessageSchema
class. The code returns the data
attribute of the result returned by the dump
method, that is, the serialized message in JSON format as the body, with the default HTTP 200 OK
status code.delete
: This method receives the id of the message that has to be deleted in the id
argument. The code calls the Message.query.get_or_404
method to return an HTTP 404 Not Found
status in case there is no message with the requested id in the underlying database. In case the message exists, the code calls the message.delete
method with the retrieved message as an argument to use the Message
instance to erase itself from the database. Then, the code returns an empty response body and a 204 No Content
status code.patch
: This method receives the id of the message that has to be updated or patched in the id
argument. The code calls the Message.query.get_or_404
method to return an HTTP 404 Not Found
status in case there is no message with the requested id in the underlying database. In case the message exists, the code calls the request.get_json
method to retrieve the key-value pairs received as arguments with the request. The code updates specific attributes in case they have new values in the message_dict
dictionary in the Message
instance: message
. Then, the code calls the message_schema.dump
method to retrieve any errors generated when serializing the updated message. In case there were errors, the code returns the errors and an HTTP 400 Bad Request
status. If the serialization didn't generate errors, the code calls the message_schema.validate
method to retrieve any errors generated while validating the updated message. In case there were validation errors, the code returns the validation errors and an HTTP 400 Bad Request
status. If the validation is successful, the code calls the update method for the Message instance to persist the changes in the database and returns the results of calling the previously explained self.get
method with the id of the updated message as an argument. This way, the method returns the serialized updated message in JSON format as the body, with the default HTTP 200 OK
status code.Now, we will create a MessageListResource
class that we will use to represent the collection of messages. Open the previously created api/views.py
file and add the following lines. The code file for the sample is included in the restful_python_chapter_06_01
folder:
class MessageListResource(Resource): def get(self): messages = Message.query.all() result = message_schema.dump(messages, many=True).data return result def post(self): request_dict = request.get_json() if not request_dict: response = {'message': 'No input data provided'} return response, status.HTTP_400_BAD_REQUEST errors = message_schema.validate(request_dict) if errors: return errors, status.HTTP_400_BAD_REQUEST try: category_name = request_dict['category']['name'] category = Category.query.filter_by(name=category_name).first() if category is None: # Create a new Category category = Category(name=category_name) db.session.add(category) # Now that we are sure we have a category # create a new Message message = Message( message=request_dict['message'], duration=request_dict['duration'], category=category) message.add(message) query = Message.query.get(message.id) result = message_schema.dump(query).data return result, status.HTTP_201_CREATED except SQLAlchemyError as e: db.session.rollback() resp = jsonify({"error": str(e)}) return resp, status.HTTP_400_BAD_REQUEST
The MessageListResource
class is a subclass of flask_restful.Resource
and declares the following two methods that will be called when the HTTP
method with the same name arrives as a request on the represented resource:
get
: This method returns a list with all the Message
instances saved in the database. First, the code calls the Message.query.all
method to retrieve all the Message
instances persisted in the database. Then, the code calls the message_schema.dump
method with the retrieved messages and the many
argument set to True
to serialize the iterable collection of objects. The dump
method will take each Message
instance retrieved from the database and apply the field filtering and output formatting specified the MessageSchema
class. The code returns the data
attribute of the result returned by the dump method, that is, the serialized messages in JSON format as the body with the default HTTP 200 OK
status code.post
: This method retrieves the key-value pairs received in the JSON body, creates a new Message
instance and persists it in the database. In case the specified category name exists, it uses the existing category. Otherwise, the method creates a new Category
instance and associates the new message to this new category. First, the code calls the request.get_json
method to retrieve the key-value pairs received as arguments with the request. Then, the code calls the message_schema.validate
method to validate the new message built with the retrieved key-value pairs. Remember that the MessageSchema
class will execute the previously explained process_category
method before we call the validate method, and therefore, the data will be processed before the validation takes place. In case there were validation errors, the code returns the validation errors and an HTTP 400 Bad Request
status. If the validation is successful, the code retrieves the category name received in the JSON body, specifically in the value for the 'name'
key of the 'category'
key. Then, the code calls the Category.query.filter_by
method to retrieve a category that matches the retrieved category name. If no match is found, the code creates a new Category
with the retrieved name and persists in the database. Then, the code creates a new message with the message
, duration
, and the appropriate Category
instance, and persists it in the database. Finally, the code returns the serialized saved message in JSON format as the body, with the HTTP 201 Created
status code.Now, we will create a CategoryResource
class that we will use to represent a category resource. Open the previously created api/views.py
file and add the following lines. The code file for the sample is included in the restful_python_chapter_06_01
folder:
class CategoryResource(Resource): def get(self, id): category = Category.query.get_or_404(id) result = category_schema.dump(category).data return result def patch(self, id): category = Category.query.get_or_404(id) category_dict = request.get_json() if not category_dict: resp = {'message': 'No input data provided'} return resp, status.HTTP_400_BAD_REQUEST errors = category_schema.validate(category_dict) if errors: return errors, status.HTTP_400_BAD_REQUEST try: if 'name' in category_dict: category.name = category_dict['name'] category.update() return self.get(id) except SQLAlchemyError as e: db.session.rollback() resp = jsonify({"error": str(e)}) return resp, status.HTTP_400_BAD_REQUEST def delete(self, id): category = Category.query.get_or_404(id) try: category.delete(category) response = make_response() return response, status.HTTP_204_NO_CONTENT except SQLAlchemyError as e: db.session.rollback() resp = jsonify({"error": str(e)}) return resp, status.HTTP_401_UNAUTHORIZED
The CategoryResource
class is a subclass of flask_restful.Resource
and declares the following three methods that will be called when the HTTP
method with the same name arrives as a request on the represented resource:
get
: This method receives the id of the category that has to be retrieved in the id
argument. The code calls the Category.query.get_or_404
method to return an HTTP 404 Not Found
status in case there is no category with the requested id in the underlying database. In case the message exists, the code calls the category_schema.dump
method with the retrieved category as an argument to use the CategorySchema
instance to serialize the Category
instance whose id
matches the specified id
. The dump
method takes the Category
instance and applies the field filtering and output formatting specified in the CategorySchema
class. The code returns the data
attribute of the result returned by the dump
method, that is, the serialized message in JSON format as the body, with the default HTTP 200 OK
status code.patch
: This method receives the id of the category that has to be updated or patched in the id
argument. The code calls the Category.query.get_or_404
method to return an HTTP 404 Not Found
status in case there is no category with the requested id in the underlying database. In case the category exists, the code calls the request.get_json
method to retrieve the key-value pairs received as arguments with the request. The code updates just the name attribute in case it has a new value in the category_dict
dictionary in the Category
instance: category
. Then, the code calls the category_schema.validate
method to retrieve any errors generated when validating the updated category. In case there were validation errors, the code returns the validation errors and an HTTP 400 Bad Request
status. If the validation is successful, the code calls the update method for the Category
instance to persist the changes in the database and returns the results of calling the previously explained self.get
method with the id of the updated category as an argument. This way, the method returns the serialized updated message in JSON format as the body, with the default HTTP 200 OK
status code.delete
: This method receives the id of the category that has to be deleted in the id
argument. The code calls the Category.query.get_or_404
method to return an HTTP 404 Not Found
status in case there is no category with the requested id in the underlying database. In case the category exists, the code calls the category.delete
method with the retrieved category as an argument to use the Category
instance to erase itself from the database. Then, the code returns an empty response body and a 204 No Content
status code.Now, we will create a CategoryListResource
class that we will use to represent the collection of categories. Open the previously created api/views.py
file and add the following lines. The code file for the sample is included in the restful_python_chapter_06_01
folder:
class CategoryListResource(Resource): def get(self): categories = Category.query.all() results = category_schema.dump(categories, many=True).data return results def post(self): request_dict = request.get_json() if not request_dict: resp = {'message': 'No input data provided'} return resp, status.HTTP_400_BAD_REQUEST errors = category_schema.validate(request_dict) if errors: return errors, status.HTTP_400_BAD_REQUEST try: category = Category(request_dict['name']) category.add(category) query = Category.query.get(category.id) result = category_schema.dump(query).data return result, status.HTTP_201_CREATED except SQLAlchemyError as e: db.session.rollback() resp = jsonify({"error": str(e)}) return resp, status.HTTP_400_BAD_REQUEST
The CategoryListResource
class is a subclass of flask_restful.Resource
and declares the following two methods that will be called when the HTTP method with the same name arrives as a request on the represented resource:
get
: This method returns a list with all the Category
instances saved in the database. First, the code calls the Category.query.all
method to retrieve all the Category
instances persisted in the database. Then, the code calls the category_schema.dump
method with the retrieved messages and the many
argument set to True
to serialize the iterable collection of objects. The dump
method will take each Category
instance retrieved from the database and apply the field filtering and output formatting specified the CategorySchema
class. The code returns the data
attribute of the result returned by the dump method, that is, the serialized messages in JSON format as the body, with the default HTTP 200 OK
status code.post
: This method retrieves the key-value pairs received in the JSON body, creates a new Category
instance and persists it in the database. First, the code calls the request.get_json
method to retrieve the key-value pairs received as arguments with the request. Then, the code calls the category_schema.validate
method to validate the new category built with the retrieved key-value pairs. In case there were validation errors, the code returns the validation errors and an HTTP 400 Bad Request
status. If the validation is successful, the code creates a new category with the specified name
, and persists it in the database. Finally, the code returns the serialized saved category in JSON format as the body, with the HTTP 201 Created
status code.The following table shows the method of our previously created classes that we want to be executed for each combination of HTTP verb and scope:
HTTP verb |
Scope |
Class and method |
|
Collection of messages |
MessageListResource.get |
|
Message |
MessageResource.get |
|
Collection of messages |
MessageListResource.post |
|
Message |
MessageResource.patch |
|
Message |
MessageResource.delete |
|
Collection of categories |
CategoryListResource.get |
|
Message |
CategoryResource.get |
|
Collection of messages |
CategoryListResource.post |
|
Message |
CategoryResource.patch |
|
Message |
CategoryResource.delete |
If the request results in the invocation of a resource with an unsupported HTTP
method, Flask-RESTful will return a response with the HTTP 405 Method Not Allowed
status code.
We must make the necessary resource routing configurations to call the appropriate methods and pass them all the necessary arguments by defining URL rules. The following lines configure the resource routing for the api. Open the api/views.py
file created earlier and add the following lines. The code file for the sample is included in the restful_python_chapter_06_01
folder:
api.add_resource(CategoryListResource, '/categories/') api.add_resource(CategoryResource, '/categories/<int:id>') api.add_resource(MessageListResource, '/messages/') api.add_resource(MessageResource, '/messages/<int:id>')
Each call to the api.add_resource
method routes a URL to a resource, specifically to one of the previously declared subclasses of the flask_restful.Resource
class. When there is a request to the API and the URL matches one of the URLs specified in the api.add_resource
method, Flask will call the method that matches the HTTP verb in the request for the specified class.
3.12.123.189