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
-
#51 Miguel Grinberg said
@Marcelo: yes, that is perfectly fine, I should have updated the article, since I'm the one who contributed the "absolute=True" feature to Flask-RESTful.
-
#52 Marcelo said
Cool! keep up the good work! I'm already accessing portions of your book's preview via Safari Online, and can't wait until it's fully published. Btw, since I was also doing an intro to MongoDB, I created a version of your flask-restful example using MongoDB as the backend for storing the tasks. More than happy to contribute the example back if that helps.
-
#53 Miguel Grinberg said
@Marcelo: I wouldn't mind adding the example to the repository so that people can compare the two implementations. Send me a pull request!
-
#54 Frode Egeland said
Thank you for this very useful tutorial. Helps a lot!
If we wanted to break it into a package with multiple files (as per http://flask.pocoo.org/docs/patterns/packages/ ), for example so the DB models live in models.py, and so on, how would the structure and imports of the various bits and pieces look?
This might be obvious to you, but I'm still learning.. :-) -
#55 Matt said
I'm quite new to programming, and sadly, I got started with Django as I heard some Flask plugins weren't supported in Python 3. I've invested a bit of time in Django to make a switch to Flask but I must say I found the 2, Flask Rest tutorials very easy to follow even though I haven't written a single line of Flask in my life:(
I wasn't totally clear on REST before but after reading these tutorials I fully understand it. Thanks a bunch:) 10/10 -
#56 Khezer said
Hi Miguel
Thanks for the great tutorial.
<h2>app.py</h2>
I have an problem with adding resource in some blueprints
like this codesfrom flask import Flask
from flask.ext.restful import Apiapi = Api()
app = Flask(name)
<h1>Initial api for app</h1>api.init_app(app)
from assets import assets as assets_blueprint
app.register_blueprint(assets_blueprint)if name == 'main':
app.run(debug=True)=======================
<hr />
assets/init.pyfrom flask import Blueprint
assets = Blueprint('assets', name)
from . import assets_list
=================
<hr />
assets/assets_list.pyfrom flask.ext.restful import Resource
from rest import apiclass AssetsList(Resource):
def get(self):
return {'data': 'ok'}api.add_resource(AssetsList, '/list')
After running the url i give 404 error in
localhost:5000/listwhere is problem?
Thanks for helpping
-
#57 Miguel Grinberg said
@Khezer: not sure. One odd thing is your "from rest import api" statementt, because the api definition is in your app.py module. Also, using a blueprint is not necessary, Flask-RESTful creates its own routes.
-
#58 Khezer said
@Miguel: Thanks for replay.
"from rest import api" its typo! in my sample code app.py file name is rest.py! and i copy/pasting the code any way
<h2>app.py</h2>
I found problem and change the code like this:from flask import Flask
app = Flask(name)
from assets import assets as assets_blueprint
app.register_blueprint(assets_blueprint)if name == 'main':
app.run(debug=True)===========================
<hr />
assets/init.pyfrom flask import Blueprint
from flask.ext.restful import Apiassets = Blueprint('assets', name)
api = Api(assets)
from . import assets_list
========================================
<hr />
assets/assets_list.pyfrom flask.ext.restful import Resource
from . import apiclass AssetsList(Resource):
def get(self):
return {'data': 'ok'}api.add_resource(AssetsList, '/list')
I define api in assets blueprint and its works!
after looking your seminar in pycon 2014 I decide using your way.
-
#59 Raghuram said
Hey, great tutorial!! I was wondering if in the get method to get a particular task by providing the task id, what if the task id contains a forward slash eg. 1/2 then how do we call the get method, I have a similar issue with my get method and I tried using everything using double quotes, using %2F instead of "/" but nothing worked!!! can somebody help me please!!!
-
#60 Miguel Grinberg said
@Raghuram: don't use a slash in your IDs, use a dash or an underscore.
-
#61 Pablo said
Great, thank you it helped a lot.
-
#62 Otger said
Thank you Miguel for this tutorials, they are easy to follow and give a lot of insight into the subject.
Could you give me some indications on how to solve the CORS problem. Whenever I try to load the json through the ajax request, it fails. On the console it ouputs (Chrome):
XMLHttpRequest cannot load http://localhost:5000/todo/api/v1.0/tasks?_=1404315571445. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3333' is therefore not allowed access.
ajax error: {"readyState":0,"responseText":"","status":0,"statusText":"error"}Where this header should be present? When serving the index.html or when serving the json? Or both?
Thanks in advance
-
#63 Miguel Grinberg said
@Otger: There is a pretty good example of the exchange required to implement CORS in the Wikipedia page: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing.
It seems your app is running on port 3333 and your Ajax API server is on 5000, correct? In that case the responses from the API server on 3333 must have a Access-Control-Allow-Origin header that refers to the http://localhost:3333 as an allowed origin. -
#64 Selcuk Bozdag said
Thanks Miguel, I read the two tutorials and tried your sample application. It was very neat. You might think of including a section about authorisation besides authentication.
-
#65 Miguel Grinberg said
@Selcuk: I discuss authorization and include an implementation of roles in my book.
-
#66 John Q said
I'm still fuzzy on how you would secure this API when consumed (legitimately) via another web page on another site.
They would have HTML or JavaScript making calls to your API and anyone looking at the source code could see it. How would they pass their credentials to your API without someone looking at source being able to see the credentials. I'm thinking "single page interface" apps where the front end folks are simply provided an API reference from which they make calls (not on same server)
-
#67 Miguel Grinberg said
@John: the credentials will be entered by the user, they will not be in the HTML/JS of the client side app. In this example it is the user who logs in, not the app. There are other situations where a client app needs to pass its own credentials to the server (maybe in addition to those of the user) and in that case the client app credentials would be in the source code an exposed. But that's not the case here.
-
#68 Romy Nguyen said
A great tutorial, thank you Miguel about Flask RESTFul API
-
#69 sebastian said
How would you solve this:
GET /users/abc123/inboxesWith flask-restful?? the username is variable, but i dont see how i could access the inboxes from the User resource, or if I have a InboxList resource, how would i send it the username as a parameter?
will add_resources(InboxList, '/users/<string:username>/inboxes') work?
Thanks! -
#70 Miguel Grinberg said
@sebastian: The "user" resource is only good for users. If you need to work with user's inboxes that is a different resource, and in that case you will create a route like the one you proposed.
-
#71 fird0s said
HI Miguel, possible that POST image/pdf type ?
thanks
-
#72 i3 said
Hey, thank you for your very easy introduction.
How can I handle the problem, when I want to POST something using @login_required, but want to use the username also in my "normal" function and not only in getPassword(user) ??
-
#73 Miguel Grinberg said
@i3: you can write any values to the "g" variable when you are in the password validation function and these will still be there when your route handler function is invoked.
-
#74 Luigi said
Sadly I got:
File "/Library/Python/2.7/site-packages/flask/exthook.py", line 87, in load_module
raise ImportError('No module named %s' % fullname)
ImportError: No module named flask.ext.restfulanybody with same issue?
-
#75 spitz said
I am somewhat confused on the best way to handle templates in a flask-restful website. For example, I have a website with a menu that links to many different .html templates. Normally, I would do something like:
@app.route("/some_template")
def some_template():
return render_template("some_template.html")For my resources (databases, etc.) I would use flask-restful. However, this mixing of @app.route and api.add_resource does not seem very elegant. Should templates be rendered from within flask-restful? In my case I have templates that make different calls to different resources.