Jan 18th, 2019 - written by Kimserey with .
Flask is a microframework for python providing building blocks to compose websites and web API quickly. It has a vast ecosystem driven by open source libraries maintained and used by many developers. Today we will see how we can setup a simple todo web API using Flask and how we can setup OpenAPI 3.0 (OAS3 - previously known as Swagger).
Flask is a microframework for python providing building blocks to compose websites and web API quickly. In this tutorial we will be creating a simple TODO allowing to perform the following actions:
We start by creating a folder and navigating to that folder:
1
2
mkdir todo_flask
cd todo_flask
We then setup a virtual environment and install Flask.
1
2
py -m venv env
pip install flask
Next we define our common file structure:
1
2
3
4
5
6
/todo_flask
/env
/todo_flask
__init__.py
__main__.py
todo_flask.py
In __init__.py
, we define the import for package import:
1
2
3
from .todo_flask import *
__version__ = '0.0.1'
In todo_flask.py
, we define a test endpoint listing todos:
1
2
3
4
5
6
7
8
9
10
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/todos")
def get_todos():
return jsonify([{
'name': 'Do groceries',
'task': 'Go to Tesco, Buy apples, lemons, yogurts.'
}])
Lastly in __main__.py
, we run the Flask application:
1
2
3
4
from todo_flask import app
if __name__ == "__main__":
app.run()
We now have a simple single endpoint API returning a list of todos. We can test that our server is running properly with:
1
py -m todo_flask
And when we hit http://localhost:5000/todos
, we should be able to see our todos.
1
[{"name":"Do groceries","task":"Go to Tesco, Buy apples, lemons, yogurts."}]
We can now define the rest of our enpoints:
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
from flask import Flask, jsonify, request
app = Flask(__name__)
todos = {}
@app.route("/todos")
def get_todos():
return jsonify(list(todos.values())), 200
@app.route("/todos", methods=['POST'])
def post_todo():
json_data = request.get_json()
if not json_data:
return jsonify({'message': 'No input data provided'}), 400
else:
# process the body
return '', 200
@app.route("/todos/<string:name>")
def get_todo(name):
if name in todos:
return jsonify(todos[name]), 200
else:
return jsonify({ 'error': 'Todo not found.' }), 400
We specify the method on the app.route
decorator with methods=['POST']
. And also specify a converter which validate the types of the parameter with <string:name>
. This converter is part of the default converter from werkzeug.routing
, the default converter are <string|uuid|int|float|any(x,y,z):param>
.
For parsing the body of the request, we use request.get_json()
from request
.
So far we have a simple web API composed by three endpoints which we can test using curl
or a UI like postman
. Another common way to test APIs is to provide an OpenAPI definition - previously known as Swagger definition. To achieve that we will be installing Flasgger.
1
2
pip install -U setuptools
pip install flasgger
We then instantiate it by importing Swagger
from flasgger
in todo_flask.py
:
1
2
3
4
5
6
7
from flask import Flask, jsonify, request
from flasgger import Swagger
app = Flask(__name__)
swagger = Swagger(app)
## Rest of the application
And when we hit http://localhost:5000/apidocs
(the default location for Swagger UI), we should now see that Swagger is running properly and is looking at http://localhost:5000/apispec_1.json
for the json specification.
Next we can see how we implement the definition of the following:
Next we can add the OAS 3.0 definition for each endpoint starting from get_todo
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route("/todos")
def get_todos():
"""
Get all todos
---
description: Get all todos.
tags:
- todos
responses:
200:
description: List of all todos.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Todo'
"""
return jsonify(list(todos.values())), 200
We start first by a description description: Get all todos.
.
Followed by tags which define a grouping for the operation, this grouping will then be reflected on the Swagger UI.
1
2
tags:
- todos
Lastly we specify the responses:
1
2
3
4
5
6
7
8
9
responses:
200:
description: List of all todos.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Todo'
The responses are defined by the status codes. It then contains the description and content which itself with the response type and the schema under that response type. For example here we return application/json
, and for the schema, we specify an array and reference to a Todo
schema.
We then define the Todo
schema in the template of Swagger:
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
app = Flask(__name__)
swagger = Swagger(app,
template= {
"swagger": "3.0",
"openapi": "3.0.0",
"info": {
"title": "TODO",
"version": "0.0.1",
},
"components": {
"schemas": {
"Todo": {
"properties": {
"name": {
"type": "string"
},
"task": {
"type": "string"
}
}
}
}
}
}
)
This will allow us to reuse it in the 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
25
@app.route("/todos", methods=['POST'])
def post_todo():
"""
Create a new todo
---
description: Create a new todo.
tags:
- todos
requestBody:
description: The todo to create.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
responses:
200:
description: Empty.
"""
data = request.get_json()
if not data:
return jsonify({'message': 'No input data provided'}), 400
else:
todos[data['name']] = data
return '', 200
We specify the body of the request using requestBody
where we specify the content with the content type and the schema which refers to the Todo
schema we used in GET
.
1
2
3
4
5
6
7
requestBody:
description: The todo to create.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
Lastly we can add the definition for the GET /todos/{name}
endpoint:
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
@app.route("/todos/<string:name>")
def get_todo(name):
"""
Get the todo with the name provided.
---
description: >
Get the todo with the name provided by
getting it using the **name** parameter provided!
tags:
- todos
parameters:
- name: name
in: path
description: Name of the todo
required: true
schema:
type: string
responses:
200:
description: A todo.
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
"""
if name in todos:
return jsonify(todos[name]), 200
else:
return jsonify({ 'error': 'Todo not found.' }), 400
We specify the paramter definition with parameters
array where we make sure to specify that the parameters comes from in: path
:
1
2
3
4
5
6
7
parameters:
- name: name
in: path
description: Name of the todo
required: true
schema:
type: string
And here is the complete todo_flask.py
code:
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from flask import Flask, jsonify, request
from flasgger import Swagger
app = Flask(__name__)
swagger = Swagger(app,
template= {
"swagger": "3.0",
"openapi": "3.0.0",
"info": {
"title": "TODO",
"version": "0.0.1",
},
"components": {
"schemas": {
"Todo": {
"properties": {
"name": {
"type": "string"
},
"task": {
"type": "string"
}
}
}
}
}
}
)
todos = {}
@app.route("/todos")
def get_todos():
"""
Get all todos
---
description: Get all todos.
tags:
- todos
responses:
200:
description: List of all todos.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Todo'
"""
return jsonify(list(todos.values())), 200
@app.route("/todos", methods=['POST'])
def post_todo():
"""
Create a new todo
---
description: Create a new todo.
tags:
- todos
requestBody:
description: The todo to create.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
responses:
200:
description: Empty.
"""
data = request.get_json()
if not data:
return jsonify({'message': 'No input data provided'}), 400
else:
todos[data['name']] = data
return '', 200
@app.route("/todos/<string:name>")
def get_todo(name):
"""
Get the todo with the name provided.
---
description: >
Get the todo with the name provided by
getting it using the **name** parameter provided!
tags:
- todos
parameters:
- name: name
in: path
description: Name of the todo
required: true
schema:
type: string
responses:
200:
description: A todo.
content:
application/json:
schema:
$ref: '#/components/schemas/Todo'
"""
if name in todos:
return jsonify(todos[name]), 200
else:
return jsonify({ 'error': 'Todo not found.' }), 400
We then now have a complete OAS3.0 definition and the UI should now display as followed:
Today we explored Flask, a microframework for python used to build websites and web APIs. We started by building a simple TODO list API with POST
and GET
endpoints taking parameters in URL and request body. We then moved on to add OpenAPI definition, previously known as Swagger definition, and defining all the elements to allow the Swagger UI to be used to test our API. Hope you liked this post, see you on the next one!