Python and Flask RESTful API Tutorial

Python and Flask RESTful API Tutorial

Introduction

        Machine Learning (ML), Artificial Intelligence (AI) and Deep Learning are getting popular and are being used almost in every domain and industry. In most of the places Python is the language being used for programming ML, AI and Deep Learning models as there are many Python libraries that support the requirements and the Python libraries have evolved over time and they are only getting better and better. To display the output data of the ML, AI or Deep Learning models or to pass input data to ML, AI or Deep Learning models using UI built on AngularJS or ReactJS or any of the other latest UI frameworks, there is the requirement of RESTful APIs to send data to UI and receive data from UI. This is where Flask comes in.
        I was looking online for basic tutorials to learn Flask RESTful APIs and most of the posts I found were very old, dating back to 2013 or 2014 which do not even work with the latest version of Python. Hence I came up with this tutorial. In this post, we will be creating an Application using Flask with GET, POST, PUT and DELETE APIs for CRUD operations on a simple Employee object. To keep things simple, all data will be kept in memory in a simple list.

Flask  

        Flask is a micro web framework written in Python and based on the Werkzeug toolkit and Jinja2 template engine. More details about Flask can be read from: Flask Website.

Requirements to Run the Application:

1. Anaconda
2. Intellij IDE with Python Community Edition Plugin

 Anaconda bundles up Python installation and the most widely used Python libraries for your machine.  If Anaconda is not installed, then Python needs to be installed separately and the individual Python libraries need to be downloaded using the pip install command which would be very time consuming. Hence Anaconda should be setup on your machine. To setup and verify if Anaconda is installed, please refer to my post on: Anaconda Setup.

In this tutorial I will help you to build REST APIs using Flask in a few simple steps.

Step 1: Create file: setup.py

setup.py is used to to build and install the application. Add basic information about the application in setup.py. Once you have this file created, you can build and install the application using the commands:
python setup.py build
python setup.py install
Here is setup.py:
from setuptools import setup

setup(name='FlaskREST',
      version='1.0.0',
      description='A Simple Flask Application'
      )

Step 2: Basic Authentication for the RESTful APIs

We will be securing the RESTful APIs with HTTP Basic Authentication. I am using username as python and password as flask. Hence we will require a username and password for accessing the APIs. For this we need to install a flask extension named as Flask-HTTPAuth. This can be installed using the following command in Anaconda Prompt:
pip install Flask-HTTPAuth

Step 3: Create file: controllers.py

In controllers.py, we will be setting up the Flask Application, HTTP  Basic Authentication and all the RESTful APIs.

Here is controllers.py:
from flask import Flask, jsonify, abort, request, make_response
from flask_httpauth import HTTPBasicAuth

app = Flask(__name__, static_url_path = "")
auth = HTTPBasicAuth()

@auth.get_password
def get_password(username):
    if username == 'python':
        return 'flask'
    return None

@auth.error_handler
def unauthorized():
    return make_response(jsonify( { 'error': 'Unauthorized access' } ), 403)
    
@app.errorhandler(400)
def not_found(error):
    return make_response(jsonify( { 'error': 'Bad request' } ), 400)

@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify( { 'error': 'Not found' } ), 404)

employees = [
    {
        'id': 1,
        'name': 'Jane',
        'department': 'Technology',
        'salary': 20000
    },
    {
        'id': 2,
        'name': 'John',
        'department': 'HR',
        'salary': 15000
    }
]
    
@app.route('/employee', methods = ['GET'])
@auth.login_required
def get_employees():
    return jsonify({ 'employees': employees})

@app.route('/employee/<int:id>', methods = ['GET'])
@auth.login_required
def get_employee(id):
    employee = [employee for employee in employees if employee['id'] == id]
    if len(employee) == 0:
        abort(404)
    return jsonify( { 'employee': employee[0] } )

@app.route('/employee', methods = ['POST'])
@auth.login_required
def create_employee():
    if not request.json or not 'name' in request.json:
        abort(400)
    employee = {
        'id': employees[-1]['id'] + 1,
        'name': request.json['name'],
        'department': request.json.get('department', ""),
        'salary': request.json['salary']
    }
    employees.append(employee)
    return jsonify( { 'employee': employee } ), 201

@app.route('/employee/<int:id>', methods = ['PUT'])
@auth.login_required
def update_employee(id):
    employee = [employee for employee in employees if employee['id'] == id]
    if len(employee) == 0:
        abort(404)
    if not request.json:
        abort(400)
    if 'name' in request.json and type(request.json['name']) != str:
        abort(400)
    if 'department' in request.json and type(request.json['department']) is not str:
        abort(400)
    if 'salary' in request.json and type(request.json['salary']) is not int:
        abort(400)
    employee[0]['name'] = request.json.get('name', employee[0]['name'])
    employee[0]['department'] = request.json.get('department', employee[0]['department'])
    employee[0]['salary'] = request.json.get('salary', employee[0]['salary'])
    return jsonify( { 'employee': employee[0] } )
    
@app.route('/employee/<int:id>', methods = ['DELETE'])
@auth.login_required
def delete_employee(id):
    employee = [employee for employee in employees if employee['id'] == id]
    if len(employee) == 0:
        abort(404)
    employees.remove(employee[0])
    return jsonify( { 'status': 'success' } )
    
if __name__ == '__main__':
    app.run(debug = True)
Here is the explanation for the code in controllers.py:

1. Create a simple Flask Application using the code:
app = Flask(__name__, static_url_path = "")
2. As mentioned above, we will be using HTTP Basic Authentication. To set this up in this application we use the code below:
auth = HTTPBasicAuth()
3. For HTTP Basic Authentication, in this tutorial I am using the username as python and password as flask. The username and password for HTTP Basic Authentication needs to be setup. The code for this is:
@auth.get_password
def get_password(username):
    if username == 'python':
        return 'flask'
    return None
4. I am defining a few error handlers to handle unauthorized authentication and to handle HTTP status codes: 400 and 404 in the application. Here is the code for this:
@auth.error_handler
def unauthorized():
    return make_response(jsonify( { 'error': 'Unauthorized access' } ), 403)
    
@app.errorhandler(400)
def not_found(error):
    return make_response(jsonify( { 'error': 'Bad request' } ), 400)

@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify( { 'error': 'Not found' } ), 404)
5. To keep things simple, all data will be kept in memory in a simple list. I am creating a list with the details of 2 employees. Here is the code for this:
employees = [
    {
        'id': 1,
        'name': 'Jane',
        'department': 'Technology',
        'salary': 20000
    },
    {
        'id': 2,
        'name': 'John',
        'department': 'HR',
        'salary': 15000
    }
]
6. Define a GET API to get all the employees. If you observe in the code, I am using @auth.login_required, which tells that in order to use this API a username and password is required. Here is the code for this:
@app.route('/employee', methods = ['GET'])
@auth.login_required
def get_employees():
    return jsonify({ 'employees': employees})
7. Define a GET API to get an employee by ID. Here I am using @auth.login_required. Here is the code for this:
@app.route('/employee/<int:id>', methods = ['GET'])
@auth.login_required
def get_employee(id):
    employee = [employee for employee in employees if employee['id'] == id]
    if len(employee) == 0:
        abort(404)
    return jsonify( { 'employee': employee[0] } )
8. Define a POST API to create an employee and return the created employee. Here I am using @auth.login_required. Here is the code for this:
@app.route('/employee', methods = ['POST'])
@auth.login_required
def create_employee():
    if not request.json or not 'name' in request.json:
        abort(400)
    employee = {
        'id': employees[-1]['id'] + 1,
        'name': request.json['name'],
        'department': request.json.get('department', ""),
        'salary': request.json['salary']
    }
    employees.append(employee)
    return jsonify( { 'employee': employee } ), 201
9. Define a PUT API to update an employee by ID and return the updated employee. Here I am using @auth.login_required. Here is the code for this:
@app.route('/employee/<int:id>', methods = ['PUT'])
@auth.login_required
def update_employee(id):
    employee = [employee for employee in employees if employee['id'] == id]
    if len(employee) == 0:
        abort(404)
    if not request.json:
        abort(400)
    if 'name' in request.json and type(request.json['name']) != str:
        abort(400)
    if 'department' in request.json and type(request.json['department']) is not str:
        abort(400)
    if 'salary' in request.json and type(request.json['salary']) is not int:
        abort(400)
    employee[0]['name'] = request.json.get('name', employee[0]['name'])
    employee[0]['department'] = request.json.get('department', employee[0]['department'])
    employee[0]['salary'] = request.json.get('salary', employee[0]['salary'])
    return jsonify( { 'employee': employee[0] } )
10. Define a DELETE API to delete an employee by ID. Here I am using @auth.login_required. Here is the code for this:
@app.route('/employee/<int:id>', methods = ['DELETE'])
@auth.login_required
def delete_employee(id):
    employee = [employee for employee in employees if employee['id'] == id]
    if len(employee) == 0:
        abort(404)
    employees.remove(employee[0])
    return jsonify( { 'status': 'success' } )
11. Lastly, here is the code to run the Flask application:
if __name__ == '__main__':
    app.run(debug = True)

Run Application:

1. To run the application, in Anaconda Prompt, navigate to your project location and execute the command:
python controllers.py
2. To run the application in Intellij IDE, right click the file controllers.py and click Run 'controllers'

API calls and results:

The application runs on the default port: 5000.
Use Basic Authentication for all APIs, with the following details:
Username: python
Password: flask

1. GET API to get all employees
    http://localhost:5000/employee

2. GET API to get an employee by ID
    http://localhost:5000/employee/1

3. POST API to create an employee
    JSON Request Body:
{
 "name": "Mark",
 "department": "Accounts",
 "salary" : 8000
}
4. PUT  API to update an employee
    http://localhost:5000/employee/2
    JSON Request Body:
{
    "name": "John",
    "department": "HR",
    "salary": 10000
}
5. DELETE API to delete an employee by ID
    http://localhost:5000/employee/1

Conclusion and GitHub link:

    In this post I have shown you how you can create a Python Flask application, build RESTful APIs and perform CRUD operations. The code used in this post is available on GitHub.
    Learn the most popular and trending technologies like Machine Learning, Angular 5, Internet of Things (IoT), Akka HTTP, Play Framework, Dropwizard, Docker, Elastic Stack, Spring Boot and Flask in simple steps by reading my most popular blog posts at Software Developer Central.
    If you like my post, please feel free to share it using the share button just below this paragraph or next to the heading of the post. You can also tweet with #SoftwareDeveloperCentral on Twitter. To get a notification on my latest posts or to keep the conversation going, you can follow me on Twitter. Please leave a note below if you have any questions or comments.

Comments

Popular Posts

REST API using Play Framework with Java

Asynchronous Processing (@Async) in Spring Boot

Elasticsearch, Logstash, Kibana Tutorial: Load MySQL Data into Elasticsearch