Developing RESTful Services with Flask
Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions.
Introduction
In this blog post I will introduce you to the Flask Framework and how to build a simple To Do REST service.
“Micro” does not mean that your whole web application has to fit into a single Python file (although it
certainly can), nor does it mean that Flask is lacking in functionality. The “micro” in microframework means
Flask aims to keep the core simple but extensible. Flask won’t make many decisions for you, such as what
database to use. Those decisions that it does make, such as what templating engine to use, are easy to change.
Everything else is up to you, so that Flask can be everything you need and nothing you don’t.
A minimal Flask application looks something like this:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'Directory Structure
Create files and directories as outlined below before you continue. If you are uncertain view the source in the github repository.
.
|__todoapi/
| |__`__init__.py
| |__app.py
| |__resources/
| | |__`__init__.py
| | |__todo_resource.py
| |__common/
| | |__`init__.py
| | |__util.py
|__venv/
|__requirements.txt
Prerequisites
In the next section we will discuss how to setup a REST service in Flask using a Flask extension library:
Flask RESTful
Setting Up
Before we set up the project we need to create a virtual environment for Python.
virtualenv is a tool to create isolated Python environments.
Install virtualenv with pip:
> pip install virtualenvWe can then create an isolated Python environment with:
> virtualenv venvWhere venv is a directory to place the new virtual environment.
In a newly created virtualenv there will also be a activate shell script. For Windows systems, activation
scripts are provided for the Command Prompt and Powershell.
On Posix systems, this resides in /ENV/bin/, so you can run:
$ source bin/activate
$ activateTo undo these changes, just run:
$ deactivateOn Windows, the equivalent activate script is in the Scripts folder:
> \path\to\venv\Scripts\activateAnd type deactivate to undo the changes. Now activate the virtual environement using the methods
outlined above based on your operating system.
We will organise the dependencies for the REST application in a requirements.txt file:
file:
requirements.txt
flask-restful
python-simplexml
If it is not obvious at this point, flask-restful is flask extension for RESTful webservices and SimpleXML
is a library for very simply manipulating XML files. It lets you harvest data from XML files, change values of attributes, print or change data of elements, create new elements, etc.
Install the packages with the following command:
> pip install -r requirements.txtThis pulls in Flask RESTful and SimpleXML and all their dependent packages.
Creating Utility Module for REST
We will create a small utility module to assist with GET, UPDATE, POST and DELETE operations:
file:
todoapi/common/util.py
from flask_restful import abort
from datetime import datetime
TODOS = [
{'id': 1, 'description': 'Create a post on REST using Flask',
'created_time': str(datetime.now()), 'modified_time': None},
{'id': 2, 'description': 'Store REST data in a database for Flask post',
'created_time': str(datetime.now()), 'modified_time': None},
...
{'id': 8, 'description': 'Write a flask tutorial for WebSockets',
'created_time': str(datetime.now()), 'modified_time': None},
{'id': 9, 'description': 'Form handling in Flask',
'created_time': str(datetime.now()), 'modified_time': None},
{'id': 10, 'description': 'Handling binary files in flask',
'created_time': str(datetime.now()), 'modified_time': None},
]
def remove_todo(key):
todo = find_todo(TODOS, key)
TODOS.remove(todo)
def update_todo(todo, key):
for idx, item in enumerate(TODOS):
if key is item['id']:
todo['id'] = key
todo['modified_time'] = str(datetime.now())
todo['created_time'] = item['created_time']
TODOS[idx] = todo
def add_todo(todo):
todo['created_time'] = str(datetime.now())
TODOS.append(todo)
def find_todo(seq, key):
return next((item for item in seq if item["id"] == key), None)
def abort_if_todo_doesnt_exist(todo_id):
if find_todo(TODOS, todo_id) is None:
abort(404, message="Todo {} doesn't exist".format(todo_id))In the above snippet we added abort_if_todo_doesnt_exist function to handle 404 errors when a resource with
the given id does not exist in the TODOS array.
Create the Resource Classes
Next we create the resource classes that consume the utility module:
file:
todoapi/resources/todo_resource.py
from flask_restful import Resource
from flask import request
from todoapi.common import util
class Todo(Resource):
def get(self, todo_id):
util.abort_if_todo_doesnt_exist(todo_id)
return util.find_todo(util.TODOS, todo_id)
def delete(self, todo_id):
util.abort_if_todo_doesnt_exist(todo_id)
util.remove_todo(todo_id)
return '', 204
def put(self, todo_id):
util.abort_if_todo_doesnt_exist(todo_id)
task = request.get_json(force=True)
util.update_todo(task, todo_id)
return task, 201
class TodoList(Resource):
def get(self):
return util.TODOS
def post(self):
task = request.get_json(force=True)
util.add_todo(task)
return task, 201In this module we have two resource classes. Todo serves single resources, and TodoList serves an array.
Wrapping up
It is time to create our entry point module to start our flask REST service:
file:
todoapi/app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from simplexml import dumps
from flask import make_response, Flask
from flask_restful import Api
from todoapi.resources import todo_resource
def output_xml(data, code, headers=None):
"""Makes a Flask response with a XML encoded body"""
resp = make_response(dumps({'response': data}), code)
resp.headers.extend(headers or {})
return resp
app = Flask(__name__)
api = Api(app, default_mediatype='application/json')
api.representations['application/xml'] = output_xml
api.add_resource(todo_resource.TodoList, '/api/v1.0/resources')
api.add_resource(todo_resource.Todo, '/api/v1.0/resources/<int:todo_id>')
output_xml is a marshaller for XML. On line 14 we are telling flask to use this marshaller for XML
representation. On lines 16 and 17 we map the endpoints to the representations.
Running and Testing the Endpoints
To run the service you need to set two environment variables:
> set FLASK_APP=app.py # export FLASK_APP=app.py -- on Unix
> set FLASK_DEBUG=1 # export FLASK_DEBUG=1 -- on UnixThen activate the virtualenv if you haven’t already done so. Navigate to the todoapi directory i.e.
cd todoapi and run the following command:
> flask runUsing cURL or a REST client of your choice test the GET endpoint:
> curl -i -X GET http://127.0.0.1:5000/api/v1.0/resources/
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 1323
Server: Werkzeug/0.12.2 Python/3.5.2
Date: Sun, 10 Dec 2017 08:28:04 GMT
[{
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Create a post on REST using Flask",
"id": 1
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Store REST data in a database for Flask post",
"id": 2
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Secure a REST service in Flask",
"id": 3
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Write another article on Flask",
"id": 4
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Secure a flask REST service using JWT",
"id": 5
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Secure a REST service in Flask using OAuth2",
"id": 6
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "SQL Migration using Flask",
"id": 7
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Write a flask tutorial for WebSockets",
"id": 8
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Form handling in Flask",
"id": 9
}, {
"modified_time": null,
"created_time": "2017-12-10 08:27:06.557315",
"description": "Handling binary files in flask",
"id": 10
}]You can also view the representation as XML:
> curl -i -H "Accept: application/xml" -X GET http://127.0.0.1:5000/api/v1.0/resources/
HTTP/1.0 200 OK
Content-Type: application/xml
Content-Length: 1645
Server: Werkzeug/0.12.2 Python/3.5.2
Date: Sun, 10 Dec 2017 08:33:00 GMT
<?xml version="1.0" ?>
<response>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Create a post on REST using Flask</description>
<id>1</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Store REST data in a database for Flask post</description>
<id>2</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Secure a REST service in Flask</description>
<id>3</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Write another article on Flask</description>
<id>4</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Secure a flask REST service using JWT</description>
<id>5</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Secure a REST service in Flask using OAuth2</description>
<id>6</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>SQL Migration using Flask</description>
<id>7</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Write a flask tutorial for WebSockets</description>
<id>8</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Form handling in Flask</description>
<id>9</id>
<modified_time>None</modified_time>
<created_time>2017-12-10 08:27:06.557315</created_time>
<description>Handling binary files in flask</description>
<id>10</id>
</response>We can get a single item using curl -i -X GET http://127.0.0.1:5000/api/v1.0/resources/1.
To delete use curl -i -X DELETE http://127.0.0.1:5000/api/v1.0/resources/1.
The POST and PUT endpoints require a body:
> curl -i -X POST http://127.0.0.1:5000/api/v1.0/resources/ -d "{\"id\": 11, \"description\": \"New Todo\"}"
HTTP/1.0 201 CREATED
Content-Type: application/json
Content-Length: 84
Server: Werkzeug/0.12.2 Python/3.5.2
Date: Sun, 10 Dec 2017 09:03:26 GMT
{
"created_time": "2017-12-10 09:03:26.744768",
"description": "New Todo",
"id": 11
}> curl -i -X PUT http://127.0.0.1:5000/api/v1.0/resources/2 -d "{\"description\": \"Update todo item 2\"}"
HTTP/1.0 201 CREATED
Content-Type: application/json
Content-Length: 140
Server: Werkzeug/0.12.2 Python/3.5.2
Date: Sun, 10 Dec 2017 09:06:25 GMT
{
"created_time": "2017-12-10 08:57:40.635545",
"description": "Update todo item 2",
"id": 2,
"modified_time": "2017-12-10 09:06:25.950571"
}Conclusion
In this post we learned how to create a REST service with Flask in Python. In addition if you have built
a flask app and are ready to take your application to the next level, you can take a look at this blog
post on Flask Production Recipes.
As usual you can find the full example to this guide in the github repository. Until the next post, keep doing cool things
.