# Python Web Api With Flask

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:

• List todos
• Get a todo by name
• Post a new todo

We start by creating a folder and navigating to that folder:

1
2


We then setup a virtual environment and install Flask.

1
2
py -m venv env


Next we define our common file structure:

1
2
3
4
5
6
/env
__init__.py
__main__.py


In __init__.py, we define the import for package import:

1
2
3

__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

@app.route("/todos")
def get_todos():
return jsonify([{
'name': 'Do groceries',
}])


Lastly in __main__.py, we run the Flask application:

1
2
3
4

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


And when we hit http://localhost:5000/todos, we should be able to see our todos.

1


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

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:


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.

## Flasgger

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 flasgger import Swagger

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:

### Response definition

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.

### Schema Type definition

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
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"
},
"type": "string"
}
}
}
}
}
}
)


### Request Body definition

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'


### Path Parameter defintion

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  ### Putting it all together 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:

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!