Designing a RESTful API using Flask-RESTful

Posted by
on under

This 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 MethodURIAction
GEThttp://[hostname]/todo/api/v1.0/tasksRetrieve list of tasks
GEThttp://[hostname]/todo/api/v1.0/tasks/[task_id]Retrieve a task
POSThttp://[hostname]/todo/api/v1.0/tasksCreate a new task
PUThttp://[hostname]/todo/api/v1.0/tasks/[task_id]Update an existing task
DELETEhttp://[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

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

188 comments
  • #1 Kuan said

    Great Tut! thumb up!

  • #2 rick said

    Loved your previous article. This looks exactly like what I was looking for - using classes instead of individual methods. I really like your clean examples and explanations. Thanks for your posts.

  • #3 Marco Massenzio said

    Great post, thanks!
    Very timely too, as this is exactly what I needed for a project of mine.

    Very minor point: you may want to add links to the other two articles.
    Good luck with the book!

  • #4 Daniel Donovan said

    Loving your tutorial very much. I would love to see how you would create RESTful api's from SQLAlchemy models.

  • #5 Danny said

    Your tutorials are some of the best I've ever read.

    You are a champion.

  • #6 Dan said

    Miguel,

    Thanks for the great tutorial. Having started with Flask based on one of your previous tutorials and then implementing Flask-Restful, it was great to see your examples and comments to compare notes...

    Cheers,

    Dan

  • #7 Akshay said

    Great articles Miguel, not only for the insight into Flask but also designing a Rest API. Would be awesome if you could discuss multi tenant design in Flask. Can't wait for your book

  • #8 Tim said

    Thanks for the example code. It was immensely helpful in getting going with flask_restful.

    I am not sure if the implementation has changed since you published this code but I would just like to point out something that tripped me up. The args object returned by parse_args() will have an entry for each argument. Any that were not passed in the PUT request have a value of None. Calling args.get() will give unexpected results as the default value will never be used.

    For example it seems that rather than doing:
    task['title'] = args.get('title', task['title'])
    you need to do:
    if args.title != None:
    task['title'] = args.title

  • #9 Miguel Grinberg said

    @Tim: not sure what's going on. Flask-RESTful defines this little class called Namespace, which inherits from dict. That is the type of the args. My approach uses the regular dict interface, yours uses the namespace syntax. Both should work the same.

  • #10 Chitrank Dixit said

    Thank you Miguel , I have searched a lot about writing the Flask-RESTful API a quick review but what you explain is really awesome.

  • #11 Dave Barndt said

    Miguel, thank you for the tutorial and the example. Flask and the new RESTful extension are terrific to use, and your tutorial and example(s) help make it so.

    I just have a quick question. Flask's url_for() function allows you to specify an "_external=True" keyword argument to provide the full URL to a resource. Is there a way to pass something similar to Flask-RESTful's fields.Url(<endpoint>) method so that when the field/URL value is marshaled and returned in a GET response, it will be a full URL?

  • #12 Miguel Grinberg said

    @Dave: Hmm. I somehow missed this when I wrote the article. Sounds like these URLs should be always external, since they are returned back to the client. I'll log a bug with Flask-Restful.

  • #13 Miguel Grinberg said

    @Dave: I have sent Flask-Restful a pull request with this change. You can see what I changed here: https://github.com/twilio/flask-restful/pull/139

  • #14 ryzhiy said

    Hi Miguel,
    Thanks for the best tutorials on Flask over the internet.
    I had a quick question on managing unicode character. My application need to handle cyrillic characters, thus instead of using "str" I specify "unicode" type for my literals.
    The issue is that my application instance one server works smoothly, but on another it fails permanently to handle properly the ajax data.
    When I send latin strings in the POST it rejects them populating the "help" error message.
    When I send non-latin strings, it receives parses the data, but fails to execute marshal scenario with an error MarshallingException: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)

    Have you also met a similar issue before?

    Thanks in advance

  • #15 Miguel Grinberg said

    @ryzhiy: have you tried using utf-8? For this you have to set the charset in the Content-Type header.

  • #16 ryzhiy said

    Yyes, I did. But this didn't help. The server accepts normally cyrillic encoded symbols and mixed strings (Cyrillic+ Latin), but not strings composed solely of Latin characters.

    I assume that this is somehow related to the web server or python versions. On the server where this doesn't work there is a gevent WSGI server installed.

  • #17 Hooman said

    Hi Miguel,

    I have a problem understanding how your authentication works here.

    @auth.get_password
    def get_password(username):
    if username == 'miguel':
    return 'python'
    return None

    This seems to look for the given username and to return the password in clear format. Now if the given password matches the returned clear password for that user, the user is authorized.

    $ curl -u miguel:python -i http://localhost:5000/todo/api/v1.0/tasks

    In my case I need to send the password in clear format to the service and hash it right there and compare it to the existing hashed password in database. If its equal, then the user is authenticated. But I don't understand how your method, is supposed to do this. May you please kindly clarify the return 'python' part? Because I have no access to the clear passwords of the users.

  • #18 Miguel Grinberg said

    @Hooman: this is a very simple example that does not deal with password hashing. If your password are hashed in the database (and they should be!) the get_password() function will return the password hash. My Flask-HTTPAuth extension includes a hash_password decorator that you can use to declare your hashing function. Example:

    @auth.hash_password
    def hash_pw(password):
    return md5(password).hexdigest()

    This will be applied to all passwords coming from users before the comparison.

    NOTE: in all these cases secure HTTP must be used, the password or the password hashes should never be sent on an unencrypted channel.

  • #19 Dave Barndt said

    Hi Miguel,

    Thanks for a great tutorial - it's been VERY helpful.. I had a quick question. I would like to be able to always specify full URLs when returning data. I see it's possible to specify "_external=True" in calls to flask.ext.restful.Api.url_for(), but it's not clear to me how to do so for the URL returned from the flask.ext.restful.fields.Url() function...?

    Thanks again for a great tutorial and examples.

    Dave

  • #20 Miguel Grinberg said

    @Dave: You are not the first that noticed that the URLs returned by Flask-Restful are not external. I have forked the project and fixed it, you can find my fork at https://github.com/miguelgrinberg/flask-restful. I have submitted a pull request to the project with the change.

  • #21 Dave Barndt said

    Hi Miguel,

    My bad, actually - I wound up asking the same question twice. I neglected to notice there was a second page of comments for the article and just thought my original question hadn't been posted, or answered! Please accept my sincere apologies. I grabbed the forked/fixed fields.py module and it seems to work great. Thank you again for a terrific tutorial and timely response, despite my lack of comment-page-navigation skills. :^)

    Dave

  • #22 Dave Barndt said

    Hi again Miguel,

    I'm having another problem that I can't make much headway on and I'm wondering if something might be going on within Flask.

    I'm using lighttpd/flup/Flask (Flask-RESTful) to serve a JSON-based RESTful API. Overall, it's working great so far. The API's Resource-based classes return objects from which Flask is creating JSON as expected, returning a "Content-Type: application/json" in the HTTP response header and the JSON string in the HTTP response body.

    What I'd like to do is compress the returned JSON if the client indicates it will accept it. I have mod_compress enabled in lighttpd.conf, with the following directive to set the MIME types to compress:

    compress.filetype = ( "application/json", "text/json", "text/javascript",
    "application/x-javascript", "text/css", "text/html", "text/plain" )
    

    The client (browser-based) is always sending an "Accept-Type: gzip, ..." header. When lighttpd returns "text/*" MIME types, it is setting the "Content-Encoding: gzip" header and compressing the data, and the client handles it fine.

    However, this is not working for the "application/json" MIME type, and I'm scratching my head. Since this request/response path is going through Flask, I'm wondering: Is Flask supposed to get involved in the compression process at all, or is everything supposed to be handled at the lighttpd level? I'm guessing the latter, but any confirmation/light you can shed would be helpful.

    Thanks again!

    Dave

  • #23 Miguel Grinberg said

    @Dave: I may be wrong, but I vaguely recall lighttpd's mod_compress only working for static content. I believe for dynamic (i.e. FastCGI) responses the compression needs to be generated by the framework, so you may need to add a gzip middleware to the wsgi app.

  • #24 Dave Barndt said

    I think I've found an initial answer to my own question. The lighttpd docs for mod_compress say lighttpd only compresses static files automatically, and even then only static files that are >= 128 bytes and < 128 MB. So it will not compress dynamic API data on my behalf.

    OK. so having said that, are there any Flask utilities to compress the JSON data and set the appropriate headers from within my RESTful API app? The HTTP response headers I would like to set would be:

    Vary: Accept-Encoding
    Content-Length: <length of gzip-ed data>
    Content-Encoding: gzip
    (...others?)

  • #25 Miguel Grinberg said

    @Dave: flup has a gzip middleware. I never used it, but looks like it might be what you need.

Leave a Comment