Designing a RESTful API using Flask-RESTful
Posted by
on underThis is the third article in which I explore different aspects of writing RESTful APIs using the Flask microframework. Here is the first, and the second.
The example RESTful server I wrote before used only Flask as a dependency. Today I will show you how to write the same server using Flask-RESTful, a Flask extension that simplifies the creation of APIs.
The RESTful server
As a reminder, here is the definition of the ToDo List web service that has been serving as an example in my RESTful articles:
HTTP Method | URI | Action |
---|---|---|
GET | http://[hostname]/todo/api/v1.0/tasks | Retrieve list of tasks |
GET | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Retrieve a task |
POST | http://[hostname]/todo/api/v1.0/tasks | Create a new task |
PUT | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Update an existing task |
DELETE | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Delete a task |
The only resource exposed by this service is a "task", which has the following data fields:
- uri: unique URI for the task. String type.
- title: short task description. String type.
- description: long task description. Text type.
- done: task completion state. Boolean type.
Routing
In my first RESTful server example (source code here) I have used regular Flask view functions to define all the routes.
Flask-RESTful provides a Resource
base class that can define the routing for one or more HTTP methods for a given URL. For example, to define a User
resource with GET
, PUT
and DELETE
methods you would write:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class UserAPI(Resource):
def get(self, id):
pass
def put(self, id):
pass
def delete(self, id):
pass
api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
The add_resource
function registers the routes with the framework using the given endpoint. If an endpoint isn't given then Flask-RESTful generates one for you from the class name, but since sometimes the endpoint is needed for functions such as url_for
I prefer to make it explicit.
My ToDo API defines two URLs: /todo/api/v1.0/tasks
for the list of tasks, and /todo/api/v1.0/tasks/<int:id>
for an individual task. Since Flask-RESTful's Resource
class can wrap a single URL this server will need two resources:
class TaskListAPI(Resource):
def get(self):
pass
def post(self):
pass
class TaskAPI(Resource):
def get(self, id):
pass
def put(self, id):
pass
def delete(self, id):
pass
api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint = 'tasks')
api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint = 'task')
Note that while the method views of TaskListAPI
receive no arguments the ones in TaskAPI
all receive the id
, as specified in the URL under which the resource is registered.
Request Parsing and Validation
When I implemented this server in the previous article I did my own validation of the request data. For example, look at how long the PUT
handler is in that version:
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['PUT'])
@auth.login_required
def update_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify( { 'task': make_public_task(task[0]) } )
Here I have to make sure the data given with the request is valid before using it, and that makes the function pretty long.
Flask-RESTful provides a much better way to handle this with the RequestParser
class. This class works in a similar way as argparse
for command line arguments.
First, for each resource I define the arguments and how to validate them:
from flask_restful import reqparse
class TaskListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, required = True,
help = 'No task title provided', location = 'json')
self.reqparse.add_argument('description', type = str, default = "", location = 'json')
super(TaskListAPI, self).__init__()
# ...
class TaskAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, location = 'json')
self.reqparse.add_argument('description', type = str, location = 'json')
self.reqparse.add_argument('done', type = bool, location = 'json')
super(TaskAPI, self).__init__()
# ...
In the TaskListAPI
resource the POST
method is the only one the receives arguments. The title
argument is required here, so I included an error message that Flask-RESTful will send as a response to the client when the field is missing. The description
field is optional, and when it is missing a default value of an empty string will be used. One interesting aspect of the RequestParser
class is that by default it looks for fields in request.values
, so the location
optional argument must be set to indicate that the fields are coming in request.json
.
The request parser for the TaskAPI
is constructed in a similar way, but has a few differences. In this case it is the PUT
method that will need to parse arguments, and for this method all the arguments are optional, including the done
field that was not part of the request in the other resource.
Now that the request parsers are initialized, parsing and validating a request is pretty easy. For example, note how much simpler the TaskAPI.put()
method becomes:
def put(self, id):
task = filter(lambda t: t['id'] == id, tasks)
if len(task) == 0:
abort(404)
task = task[0]
args = self.reqparse.parse_args()
for k, v in args.iteritems():
if v != None:
task[k] = v
return jsonify( { 'task': make_public_task(task) } )
A side benefit of letting Flask-RESTful do the validation is that now there is no need to have a handler for the bad request code 400 error, this is all taken care of by the extension.
Generating Responses
My original REST server generates the responses using Flask's jsonify
helper function. Flask-RESTful automatically handles the conversion to JSON, so instead of this:
return jsonify( { 'task': make_public_task(task) } )
I can do this:
return { 'task': make_public_task(task) }
Flask-RESTful also supports passing a custom status code back when necessary:
return { 'task': make_public_task(task) }, 201
But there is more. The make_public_task
wrapper from the original server converted a task from its internal representation to the external representation that clients expected. The conversion included removing the id
field and adding a uri
field in its place. Flask-RESTful provides a helper function to do this in a much more elegant way that not only generates the uri
but also does type conversion on the remaining fields:
from flask_restful import fields, marshal
task_fields = {
'title': fields.String,
'description': fields.String,
'done': fields.Boolean,
'uri': fields.Url('task')
}
class TaskAPI(Resource):
# ...
def put(self, id):
# ...
return { 'task': marshal(task, task_fields) }
The task_fields
structure serves as a template for the marshal
function. The fields.Url
type is a special type that generates a URL. The argument it takes is the endpoint (recall that I have used explicit endpoints when I registered the resources specifically so that I can refer to them when needed).
Authentication
The routes in the REST server are all protected with HTTP basic authentication. In the original server the protection was added using the decorator provided by the Flask-HTTPAuth extension.
Since the Resouce
class inherits from Flask's MethodView
, it is possible to attach decorators to the methods by defining a decorators
class variable:
from flask_httpauth import HTTPBasicAuth
# ...
auth = HTTPBasicAuth()
# ...
class TaskAPI(Resource):
decorators = [auth.login_required]
# ...
class TaskAPI(Resource):
decorators = [auth.login_required]
# ...
Conclusion
The complete server implementation based on Flask-RESTful is available in my REST-tutorial project on github. The file with the Flask-RESTful server is rest-server-v2.py.
You can also download the entire project including both server implementations and a javascript client to test it:
Download REST-tutorial project.
Let me know if you have any questions in the comments below.
Miguel
-
#176 Miguel Grinberg said
@Federico: if CORS is properly configure you should have no problem with hosting your client application and your API on different hosts, ports or both.
-
#177 Shrey Batra said
Thanx for this blog..!! Really appreciated the time you put in to make us understand it easy.
-
#178 Maggie said
Thanks-this is a great tutorial!
-
#179 Anil said
Hi Miguel - I am unable to install Flask-RESTful on my pi.
I have python, then could get Flask. But not Flask-RESTful.
Please advice. -
#180 Miguel Grinberg said
@Anil: do you get any errors when you install it? You need to provide more details about the problem.
-
#181 Nitin George Cherian said
Hello Miguel,
Suppose I need to do a call /todo/api/v1.0/tasks/ by passing either the task_id (int) or title (str), do I need to add a class for each task_id and title like so:
class TaskAPI(Resource):
def get(self, id):
passdef put(self, id): pass def delete(self, id): pass
class TaskAPIByTitle(Resource):
def get(self, title):
passapi.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint = 'tasks')
api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint = 'task')
api.add_resource(TaskAPIByTitle, '/todo/api/v1.0/tasks/<title>', endpoint = 'task_title') -
#182 Miguel Grinberg said
@Nitin: Yes, either that, or else you can create a single resource that takes a string parameter in the URL, and in the implementation try to convert that parameter to a number to load the task by id, or else load it by name.
-
#183 Waled Meselhy said
how to achieve this url with flask-restfull
get request on http://[hostname]/todo/api/v1.0/tasks/[task_id]/undo -
#184 Miguel Grinberg said
@Waled: that is really not a RESTful URL. Undoing is not something the server normally cares about, in my opinion this belongs in the client-side application.
-
#185 Nirob Sarkar said
how to get global current g object in flask restplus . i am using flask_jwt_extended token
-
#186 Miguel Grinberg said
@Nirob: the
flask.g
object should be available to use as usual, regardless of the REST framework that you use. -
#187 Francis A Reader said
Hi just bought 2nd Ed of Flask Web dev (o'reilly) book.
Q? Any reason you didn't update or include usecase of using flash-restful extension in the book?
-
#188 Miguel Grinberg said
@Francis: You do not need Flask-RESTful to write a REST API, it is entirely optional. If you like it, there is no reason why you shouldn't use it. Keep in mind that this extension hasn't been maintained in a long time, though.