REST on Flask

After I introduced you to REST, I’m going to give you a starting boost on implementing your first REST API using Flask, a Python web framework.

Flask logo

Installation

Python

If you haven’t installed Python, you can find the download page on this link. I’ll be using Python 3 across this post.

Virtual Environment

It’s good practice to create a virtual environment for each Python project. This allows you to install additional packages without compromise other projects. Imagine you were using Flask 0.11 in your school classes, but here you were using the latest version (to date) Flask 0.13. With virtual environments, you can have a different set of packages for each project. Pretty cool, huh?

So let’s create one of this guys. Open your terminal and write the following, preferentially, inside your project’s directory.

# create a new virtual environment (venv)
python3 -m venv /path/to/new/virtual/environment
# change directory to the venv root you've just created
cd /path/to/new/virtual/environment
# activate this venv
source bin/activate

When you finish working on your project at the end of the day, you should deactivate your project’s virtual environment.

# activate this venv
deactivate

Flask

With the virtual environment activated, you install the packages you’re going to need. One of those being Flask.

# download and install Flask
pip install Flask

You will want to keep a record of every package you install. That’s because if you were to deploy the app to a serve — AWS, for instance —, it would need to know what packages to install for your project to work properly.

So you just print all the installed packages into a file called requirements.txt. Remember that you must run this command with your project’s virtual environment activated.

# print your project's package set into a file named requirement.txt
pip freeze > requirements.txt

You should keep this file updated by repeating this command every time you modify your package set.

Overview

So let’s see how an endpoint method is like. Don’t be scared, if you don’t understand it at first. I will walk you through each line.

from flask import Flask
(...)
application = Flask(__name__)
@application.route(/api/v1/users/self/songs/', methods = ['GET'])
@requires_auth
def get_user_songs(user):
args = request.args

offset = args.get('offset') if 'offset' in args else 0
limit = args.get('limit') if 'limit' in args else None
    # Fetch songs from database using SQL Alchemy
session = Session()
data = crud_song.get_all_songs_from_user(session,
offset = offset,
limit = limit,
user_id = user.id)
session.close()
    # transform python object into a json object  
data_json = jsonify(SongSerializer.serialize(data, many = True))
    return data_json

Defining the endpoint

So the first thing you have to do is importing what the code needs to work. You’ll figure out what it is while you’re writing. It would be much easier, if you used an IDE like PyCharm.

Then, I defined a Flask application from which I will invoke some methods.

Before the get_user_songs method’s declaration, I defined the URL from which this method triggers its execution. I annotated this method with application.route which can have the following parameters:

  • the URL that routes to this method;
  • a list of methods (verbs) that route to this method.

Therefore, the Python method get_user_songs is triggered whenever you perform a request GET /api/v1/users/self/songs/.

On the next line, you see another annotation, requires_auth, which restricts this route only for authenticated users. You’ll see how this work out later on this post.

This method can receive some query arguments and we extract them from the client’s request with args = request.args. If there are in fact parameters defined as offset and limit, we get those. Otherwise, we assign some default values to them.

If you needed to extract some JSON from the request body (common when you receive POST or PUT requests), you would do it as following.

# extract JSON data from request body
data = request.get_json()
# extract argument
offset = data['offset']

Then you can fetch the resources from some database using and ORM like SQL Alchemy. I’ll not drive you into this library, but I can leave you with some code of what it looks like.

def get_all_songs_from_user(session: Session, offset: int = 0, limit: int = 10, song_title: str = None, song_artist: str = None, user_id: int = None) -> list:
if song_title and song_artist:
songs_result = session.query(Song)\
.filter_by(title = song_title, artist = song_artist, user_id = user_id)\
.offset(offset)\
.limit(limit)
elif song_title:
songs_result = session.query(Song)\
.filter_by(title = song_title, user_id = user_id)\
.offset(offset)\
.limit(limit)
elif song_artist:
songs_result = session.query(Song)\
.filter_by(artist = song_artist, user_id = user_id)\
.offset(offset)\
.limit(limit)
else:
songs_result = session.query(Song)\
.filter_by(user_id = user_id)\
.offset(offset)\
.limit(limit)\
.all()
return songs_result

You can see that I’m only doing some queries, depending on the information I am being given to in the method’s parameters. At the very end, I return a list of songs.

Before you wonder how the Song model looks like…

class Song(Base):
__tablename__ = "songs"

id = Column(Integer, Sequence("song_id_seq"), primary_key = True)
title = Column(String(100))
artist = Column(String(80))
album = Column(String(80))
release_year = Column(SmallInteger)
url = Column(String(300))
playlists = relationship("Playlist", secondary = "playlist_song")

user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates = "songs")

def __init__(self, title, artist, album, release_year, url, user_id):
self.title = title
self.artist = artist
self.album = album
self.release_year = release_year
self.url = url
self.user_id = user_id

def __repr__(self):
return "<Song(id = '%i', title = '%s', album = '%s', artist = '%s', release_year = '%d', url = '%s')>" \
% (self.id, self.title, self.artist, self.artist, self.release_year, self.url)

Back to the get_user_songs method. After we retrieve the songs from the database, we have to convert those python objects into JSON to be sent to the client over HTTP. So I created the following static method.

class SongSerializer:
@classmethod
def serialize(cls, data, many: bool = False):
if many:
serializer = []
for song in data:
serializer.append({'id':song.id,
'title':song.title,
'artist':song.artist,
'album':song.album,
'releaseYear':song.release_year,
'url':song.url})
else:
serializer = {'id':data.id,
'title':data.title,
'artist':data.artist,
'album':data.album,
'releaseYear':data.release_year,
'url':data.url}
return serializer

I had found it easier to extract each atribute and built myself a simpler python object to be later converted into JSON.

Finally, I pass the data into that serialiser and the many tells the method if the data represents multiple objects or just one, and then I just return this JSON object.

data_json = jsonify(SongSerializer.serialize(data, many = True))

There are many ways you can achieve this. This is one of those.

Authentication

Now let’s take a look at how I created a common gateway for every method that needs an authenticated user. Do you remember the requires_auth annotation? Let’s dive into it.

def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers['Authorization']
        if not token:
abort(401)
        session = Session()
user = crud_user.get_user_by_token(session, token)
session.close()
        if not user:
abort(401)

return f(user, *args, **kwargs)

return decorated

First, I extract the Authorization header from the HTTP request. If there isn’t one, I immediately return a 401 Unauthorised error. If I’ve survived to that, I try to match the secret token with its user. If it is all good, I finally return the function I’ve wrapped and pass the user’s data as argument.


Now you’ve just gotten a quick overview about how to implement REST using Flask! Now you can open that bottle of fine wine you have been saving for a special moment like this.