1. Basic restible App

Note

You can get the full source code for this example here.

This is a very basic example of how to expose a model over HTTP as REST API. The example below is geared toward using the least amount of code to achieve it’s goals.

For this example we’re using restible with Flask and SQLAlchemy since it’s easy to set up, allows for self-contained easy to follow code and simple code. You can also use other underlying frameworks through other integrations like restible-django or restible-appengine.

We will build a very simple blogging app. In this example it will have just one resource, the BlogPost that will be exposed over HTTP on /api/post URL.

1.1. Start with an empty Flask app

We use the most basic flask structure there is: everything is contained within one main.py file. The app is very simple so there’s not need to split it up yet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- coding: utf-8 -*-
""" A very basic example of how to expose a model over HTTP as REST API. """
from __future__ import absolute_import, unicode_literals

# stdlib imports
from datetime import datetime

# 3rd party imports
import flask
import flask_sqlalchemy
import restible
import restible.util
import restible_flask
from sqlalchemy.exc import SQLAlchemyError


db = flask_sqlalchemy.SQLAlchemy()
DATETIME_FMT = '%Y-%d-%m %H:%M:%S'


def create_app():
    app = flask.Flask(__name__)
    app.config.update({
        'DEBUG': True,
        'SQLALCHEMY_DATABASE_URI': 'sqlite:///data.sqlite',
        'SQLALCHEMY_TRACK_MODIFICATIONS': False,
    })

    with app.app_context():
        db.init_app(app)
        db.create_all()


if __name__ == '__main__':
    app = create_app()
    app.run(port=5000)

And here are the project requirements for this app:

1
2
3
4
5
Flask==1.0.2
Flask-SQLAlchemy==2.3.2
restible==0.11
restible-flask==0.2
pytest==4.4.0

1.2. BlogPost model

Next we create a simple model to persist our blog posts. Only the bare minimum here as that’s not the point.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class BlogPost(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)

    def serialize(self):
        return {
            'id': self.id,
            'title': self.title,
            'content': self.content,
            'created_at': self.created_at and self.created_at.strftime(DATETIME_FMT),
            'updated_at': self.updated_at and self.updated_at.strftime(DATETIME_FMT),
        }

1.3. Create REST Resource for BlogPost

1
2
3
4
class BlogPostResource(restible.RestResource):
    name = 'post'
    route_params = [{"name": "post_pk"}]

This is the REST resource associated with the BlogPost model. We’re using resource.RestResource which comes with least amount of functionality built-in. Everything has to be done manually. There are more base resource classes provided by restible and third party libraries.

Here we just want to kick things of with the simplest one (rarely used in real apps).

1.3.1. Query blog posts

1
2
3
    def rest_query(self, request, params, payload):
        posts = BlogPost.query.all()
        return 200, [x.serialize() for x in posts]

Here we define the generic GET /api/post route. In our case it will return all blog posts stored by the backend.

In real app, this will probably also support some filtering of the results and possibly pagination as well.

1.3.2. Get single post

1
2
3
4
5
6
7
    def rest_get(self, request, params, payload):
        post_id = int(self.get_pk(request))
        item = BlogPost.query.filter(BlogPost.id == post_id).one_or_none()
        if item:
            return 200, item.serialize()
        else:
            return 404, {'detail': "Blog post #{} not found".format(post_id)}

This is the detail route to get a blog post by ID (GET /api/post/<post_id>). restible provides a handy method for getting the route param associated with the request: RestResource.get_pk. It will take into account the

1.3.3. Create new blog post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    def rest_create(self, request, params, payload):
        session = BlogPost.query.session

        try:
            # Our basic validation only checks if all fields are present.
            missing = frozenset(['title', 'content']) - frozenset(payload.keys())
            if missing:
                raise restible.BadRequest("Missing fields: {}".format(missing))

            # Create a new BlogPost record and commit it to database.
            post = BlogPost(**payload)
            session.add(post)
            session.commit()

            return 201, post.serialize()

        except SQLAlchemyError as ex:
            session.rollback()
            return 500, {'detail': "DB ERROR: {}".format(ex)}

Next up is the blog post create handler (POST /api/post). In this basic example we only do a very simple validation - are all the required fields present. You can have a more complex validation logic in real life, but as we’ll learn later in this tutorial series, restible has some tools for that as well. Here we won’t be using them to keep things as simple as possible.

1.3.4. Update blog post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    def rest_update(self, request, params, payload):
        session = BlogPost.query.session
        post_id = int(self.get_pk(request))

        # Find the blog post we want to update.
        post = BlogPost.query.filter(BlogPost.id == post_id).one_or_none()
        if post is None:
            return 404, {'detail': "Blog post #{} not found".format(post_id)}

        # Make sure we do not overwrite read only fields.
        read_only = ['id', 'created_at', 'updated_at']
        for field in read_only:
            payload.pop(field, None)

        try:
            # Update the database record and commit.
            restible.util.update_from_values(post, payload)
            session.commit()

            return 200, post.serialize()

        except SQLAlchemyError as ex:
            session.rollback()
            return 500, {'detail': "DB ERROR: {}".format(ex)}

Here we allow the users to update any existing post (PUT /api/post/<post_id>). The code is pretty self-explanatory, but there are a few things we can point out here. As you can see we remove few values from the payload to prevent overwriting them (they are set automatically by the backend and user should not be able to overwrite those).

Another thing is the use of restible.util.update_from_values. This is a small helper function that will take each item in values (second argument) and set the corresponding attribute on the object passed to it as the first argument (post in this case). The first argument doesn’t have to be a model, any python object will do (as long as you can set each of the properties passed in values).

1.3.5. Delete blog post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    def rest_delete(self, request, params, payload):
        session = BlogPost.query.session
        post_id = int(self.get_pk(request))

        try:
            BlogPost.query.filter(BlogPost.id == post_id).delete()
            session.commit()
            return 204, {'detail': "Blog post #{} deleted".format(post_id)}
        except SQLAlchemyError as ex:
            session.rollback()
            return 500, {'detail': "DB ERROR: {}".format(ex)}

And last but not least: deleting posts (DELETE /api/post/<post_id>). Not much left to say here, just get the ID of the requested post and delete it from database.

1.4. Setup Flask URLs

Now that we have our API resource defined, the last thing to do is setup the flask URL mappings. For that, we will use an integration library restible-flask and the Endpoint class provided by it. You just need to pass the resource base URL and the associated resource class.

The resource <-> URL mapping is the one thing that will be very differente depending on what framework/libraries are you using to power restible. restible-flask, restible-django and restible-appengine all have a different way of mapping the URLs, for more information consult the docs for the library of your choosing.

In general, the resource definitions (not models) should be easy to move when you change the underlying libraries framewroks, but the mapping of those resources onto URL fully depends on the underlying library and thus can be very different.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def create_app():
    app = flask.Flask(__name__)
    app.config.update({
        'DEBUG': True,
        'SQLALCHEMY_DATABASE_URI': 'sqlite:///data.sqlite',
        'SQLALCHEMY_TRACK_MODIFICATIONS': False,
    })

    with app.app_context():
        db.init_app(app)
        db.create_all()

        restible_flask.Endpoint.init_app(app, resources=[
            ['/api/post', BlogPostResource],
        ])

    return app

1.5. Next steps

In the next section we will reimplement the same app, but this time we will use ModelResource instead of RestResource.