Restful API Design using Flask-RESTful (Part 1)
Python is by far one of the best languages I’ve used to write programs so far. The ease of writing code as well as how intuitive it is to understand the language nuances make it a good choice for beginners or experts alike.
Like most Python programs, Flask is built on Python philosophies which in summary make using the package API as easy as eating ice-cream.
Don’t take my word for it, head over to their docs to see what I mean.
Starting up a server is just a few lines
from flask import Flask
app = Flask(__name__)
if __name__ == '__main__':
app.run(debug=True)
Doing this and then running it, we see that the Python server starts off and shows that it is listening on port 7000 (The default port number).
If you haven’t already installed flask, run
pip install Flask
within your virtual environment to do so.
For this exercise, we shall be making use of Flask-RESTful, a tool that makes Flask development much easier.
Setting up a single endpoint in Flask is done this way.
from flask import request, Flask
app = Flask(__name__)
@app.route('/someendpoint', methods=['GET', 'POST']
def endpoint_callback():
if request.method == 'GET':
return "Some random string"
if request.method == 'POST':
return "From the post method"
if __name__ == '__main__':
app.run(debug=True)
Using this snippet, we can capture both GET
and POST
requests headed at http://some.domain/someendpoint
and handle the different cases using the request.method
string value.
To do something similar on Flask-RESTful, the mechanics are similar but it can be a lot more understandable at a glance.
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class EndpointResource(Resource):
def get(self):
return "Some random string"
def post(self):
return "From the post method"
api.add_resource(EndpointResource, '/someendpoint')
if __name__ == '__main__':
app.run(debug=True)
Similar to the previous code snippet, we need to create Flask instance to run at the very least. The configuration, however, in this case is a lot different than the previous one. Here the different endpoints are configured using the URL pattern and resource classes. These classes contain method implementations corresponding to each of the HTTP verbs. i.e GET
method behaviour is implemented in get()
function of the resource class, POST
method behaviour is implemented in post()
function of the resource class e.t.c. This way we can think of multiple benefits,
- Easier to read syntax
- With more separation e.g of resource classes, we would achieve even far more organization of the project in general.
- List and Dictionary return values are automatically parsed using
jsonify
to their corresponding Javascript Data structures. - We have the ability to combine this with the blueprint to get even more modular overall architecture.
To ensure we have a good understanding of this, we shall demo this with a simple inventory management system.
We would require the following endpoints in the application (for an MVP).
-
GET /items
- Returns a list of all the items in the inventory -
POST /items
- Create a new item in the inventory -
GET /item/<id>
- Get a single item byid
-
PUT /item/<id>
- Update a single item byid
-
DELETE /item/<id>
- Delete a single item byid
For this example, we shall be storing all the values within the class for simplicity.
We will start with the code for the Items
resource and then move on to the SingleItem
resource. Your application code should look something like this
from cerberus import Validator
from datetime import datetime
from uuid import uuid64
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
items = {}
class Items(Resource):
def get(self):
return items
def post(self):
body = request.json
try:
item = {k: body[k] for k in ('name', 'quantity')}
catch Exception:
return 'Error while parsing request', 500
# Generate random item id
id = uuid64()
item['date_created'] = datetime.now()
item['date_modified'] = datetime.now()
# We shall be validating the request body data against this
schema = {
'name': {'required': 'True', 'type': 'string'},
'quantity': {'type': 'integer', 'min': 10},
'date_created': {'type': 'datetime'},
'date_modified': {'type': 'datetime'}
}
v = Validator(schema)
if not v(item):
return "There was something wrong with your arguments", 400
item[id] = id
items[id] = item
return item, 201
api.add_resource(Items, '/items/')
if __name__ == '__main__':
app.run(debug=True)
A couple of things stand out here for me. The first thing that comes to mind is the fact that we’re making use of cerberus to validate our dictionary structure. This works simply by making use of dictionary format schemas which define constraints for keys in the dictionary we’re trying to validate.
We are also making use of the uuid64()
function here to generate random object ids. The rest is simple logic to add the item to a dictionary for POST
requests and return a list of items for a GET
request.
The SingleItem
is similar to what we have just done. The only difference IMO is the fact that we’re making use of a single object as opposed to a few.
You resource endpoints should look like this.
...
class SingleItem(Resource):
def get(self, id):
try:
item = items[id]
except IndexError:
return "Item could not be found", 404
return item
def put(self, id):
try:
item = items[id]
except IndexError:
return "Item could not be found", 404
body = request.json
item['name'] = body['name'] or item['name']
item['quantity'] = body['quantity'] or item['quantity']
item['date_created'] = datetime.now()
item['date_modified'] = datetime.now()
# We shall be validating the request body data against this
schema = {
'name': {'required': 'True', 'type': 'string'},
'quantity': {'type': 'integer', 'min': 10},
'date_created': {'type': 'datetime'},
'date_modified': {'type': 'datetime'}
}
v = Validator(schema)
if not v(item):
return "There was something wrong with your arguments", 400
item[id] = id
items[id] = item
return item
def delete(self, id):
try:
item = items[id]
except IndexError:
return "Item could not be found", 404
return "Item has been deleted", 204
api.add_resource(SingleItem, '/items/<string:id>')
With this code, we have setup the 2 resource classes that we need for a very simple Inventory management system REST API.
As you would have observed the Flask-RESTful syntax makes this hyper easy for us. Apart from the features shown here, a number of other things exist to make our life even much easier. Some of these include marshalling dictionaries to be returned as JSON, URL parameter validation and Blueprint support.
In the next tutorial we shall be showing you how to refactor your code to make use of a SQL Databases.
Cheers.