Developing RESTful Flask APIs with Python: A Comprehensive Guide 101

By: Published: November 5, 2021

Flask API FI

To create a RESTful Flask API, you’ll leverage Flask and Python throughout this post. To begin, you’ll create an endpoint that returns Static Data (dictionaries). Following that, you’ll design a class with two specializations as well as a few endpoints for inserting and retrieving instances of these classes. Finally, you’ll look at how to use a Docker container to run the Flask API.

Table of Contents

Why choose Python?

Python is becoming increasingly popular as a programming language for developing applications. According to a recent StackOverflow analysis, Python is one of the fastest-growing programming languages, having recently eclipsed Java in terms of the number of queries answered on the platform. The language is also showing signs of widespread popularity on GitHub, where it ranks third in terms of the number of open Pull Requests in 2016.

Flask API - Graph
Image Source

Every part of Python is being improved by the large community that is emerging around it. A growing number of open-source libraries are being produced to handle a variety of topics, including AI, Machine Learning, and Web Development. Aside from the outstanding community assistance, the Python Software Foundation provides excellent documentation for new adopters to grasp the basics quickly.

Why choose Flask API?

Flask API - Logo
Image Source

When it comes to Python Web Programming, two frameworks are commonly used: Django API and Flask API. Django is older, wiser, and a little more well-known. This framework has over 28K stars on GitHub, 1.5K contributors, 170 releases, and 11K forks. Django is the subject of about 1.2% of queries submitted on StackOverflow in any given month.

Although less well-known, Flask API is not far behind. Flask has around 30K stars on GitHub, 445 contributors, 21 releases, and nearly 10,000 forks. Up to 0.2% of queries submitted on StackOverflow in a given month are about Flask API.

Despite the fact that Django is older and has a larger community, Flask API has advantages. Flask API was designed from the ground up with Scalability and Simplicity in mind. When compared to their Django equivalents, Flask Apps are noted for being lightweight. The developers of Flask refer to it as a microframework, with micro referring to the goal of keeping the core basic but flexible. Flask isn’t going to make many decisions for us, like which Database to use or which template engine to utilize. Finally, Flask API comes with thorough documentation covering everything a developer needs to know to get started.

Flask API is an excellent choice for constructing RESTful APIs since it is lightweight, simple to use, well-documented, and widely used.

Simplify Python ETL with Hevo’s No-code Data Pipeline

A fully managed No-code Data Pipeline platform like Hevo Data helps you integrate and load data from 100+ Different Sources (40+ Free Data Sources such as Python) to a Data Warehouse or Destination of your choice in real-time in an effortless manner. Hevo with its minimal learning curve can be set up in just a few minutes allowing the users to load data without having to compromise performance. Hevo further provides a Native REST API connector that allows loading data from non-native or custom sources for free automating your data flow in minutes without writing any line of code.

Get Started with Hevo for Free

It helps transfer data from a source of your choice to a destination of your choice. Its strong integration with umpteenth sources allows users to bring in data of different kinds in a smooth fashion without having to code a single line. 

Check out some of the cool features of Hevo:

  • Completely Automated: The Hevo platform can be set up in just a few minutes and requires minimal maintenance.
  • Connectors: Hevo supports 100+ Integrations to SaaS platforms, files, Databases, analytics, and BI tools. It supports various destinations including Google BigQuery, Amazon Redshift, Snowflake, Firebolt Data Warehouses; Amazon S3 Data Lakes; and MySQL, SQL Server, TokuDB, DynamoDB, PostgreSQL Databases to name a few.  
  • Real-Time Data Transfer: Hevo provides real-time data migration, so you can have analysis-ready data always.
  • 100% Complete & Accurate Data Transfer: Hevo’s robust infrastructure ensures reliable data transfer with zero data loss.
  • Scalable Infrastructure: Hevo has in-built integrations for 100+ Sources (including 40+ free sources) that can help you scale your data infrastructure as required.
  • 24/7 Live Support: The Hevo Team is available round the clock to extend exceptional support to you through chat, email, and support calls.
  • Schema Management: Hevo takes away the tedious task of schema management & automatically detects the schema of incoming data and maps it to the destination schema.
Sign up here for a 14-Day Free Trial!

Bootstrapping a Flask Application

First and foremost, various dependencies will need to be installed on the development system. Python 3, pip (Python Package Index), and Flask are required to get started. Fortunately, installing these dependencies is a simple process.

A) Installing Python 3

If you’re running a current version of a widespread Linux distribution (such as Ubuntu), you’re likely to already have Python 3 installed. If you’re using Windows, you’ll almost certainly need to install Python 3, as this operating system doesn’t come with any. On Mac OS, Python 2 is installed by default, and you must install Python 3 manually.

After installing Python 3 on your machine, you can execute the following command to ensure that everything is working correctly:

python --version
# Python 3.6.2

If you’re using a different Python version, the command above can return a different result. What matters is that the output starts with Python 3 rather than Python 2. If the latter is the case, you can attempt the python3 —version. If this command achieves the desired result, you must replace all commands in the rest of the article.

B) Installing pip

pip is the recommended Python Package Installation tool. While the official installation page claims that pip is already installed if you use Python 2 >= 2.7.9 or Python 3 >= 3.4, installing Python using apt on Ubuntu does not. So, let’s see if you need to install pip individually or if it’s already installed.

# we might need to change pip by pip3
pip --version
# pip 9.0.1 ... (python 3.X)

If the result of the command above looks like pip 9.0.1… (python 3.X), you’re set to go. You can try replacing pip with pip3 if you get pip 9.0.1… (python 2.X). If you can’t find Pip for Python 3 on your system, you can install it using the steps found here.

C) Installing Flask

You already know what Flask API is and what it can do. As a result, let’s concentrate on installing it on the system and seeing if you can get a simple Flask application to work. The first step is to install Flask with pip:

# we might need to replace pip with pip3
pip install Flask

You’ll create a file called hello.py and add five lines of code to it after installing the program. You don’t need to nest this file in a new directory because you’ll only use it to check if Flask API was installed correctly.

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
  return "Hello, World!"

All you need to handle HTTP requests and return a “Hello, World!” message are these five lines of code. To run it, you must first export the FLASK APP environment variable and then run flask:

# flask depends on this env variable to find the main file
export FLASK_APP=hello.py

# now we just need to ask flask to run
flask run
# * Serving Flask app "hello"
# * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

To run Flask directly on Ubuntu, you may need to change the $PATH variable. Touch /.bash aliases, then echo “export PATH=$PATH:/.local/bin” >> /.bash aliases.

After running these scripts, you can access the application by going to http://127.0.0.1:5000/ in a browser or using curl http://127.0.0.1:5000/.

Flask API - First Program
Image Source

D) Virtual Environments (virtualenv)

Although PyPA (the Python Packaging Authority) recommends pip for installing Python programs, you’ll need to handle our project’s dependencies with another package. Although pip enables package management via the requirements.txt file, it lacks some aspects that make it suitable for use on major projects operating on a variety of production and development computers. The following are the issues that create the most problems:

  • Pip installs packages globally, making different versions of the same package on the same machine difficult to manage.
  • All dependencies and sub-dependencies must be listed explicitly in requirements.txt, which is a time-consuming and error-prone manual process.

Pipenv will be used to resolve these concerns. Pipenv is a dependency manager that allows packages to be installed per project while isolating projects in private environments. If you’ve used NPM or Ruby’s bundler, you’ll recognize the essence of this utility.

Let’s create a new directory to hold our source code before we start building a genuine Flask application. In this tutorial, you’ll make Cashman, a tiny RESTful API that lets users monitor their income and expenses. As a result, you’ll establish the cashman-flask-project directory. After that, you’ll launch the project and manage our dependencies with pipenv.

# create our project directory and move to it
mkdir cashman-flask-project && cd cashman-flask-project

# use pipenv to create a Python 3 (--three) virtualenv for our project
pipenv --three

# install flask a dependency on our project
pipenv install flask

The second code line sets up a virtual environment, which will house all dependencies, and the third step adds Flask as the first dependency. If you look in the directory of the project, you can see that two files are created as a result of running these commands:

  • Pipfile is a file that holds information about the project, such as the Python version used and the packages needed.
  • Pipenv.lock is a file that specifies which version of each package the project relies on and its transitive dependencies.

E) Python Modules

Like other prominent programming languages, Python contains modules, which allow programmers to organize source code by subjects or functionalities. Modules in Python are files grouped in folders that can be imported by other Python scripts, similar to Java packages and C# namespaces. You only need to create a folder and add an empty file called __init__.py to it to build a module in a Python program.

Let’s get started on the application’s initial module. This will be the core module, including all of your RESTful APIs. Let’s make a new directory, manca, inside the one made for the application. The main manca-flask-project directory will retain metadata about the project, such as its dependencies, whereas this new one will be the module, containing Python scripts.

# create source code's root
mkdir manca && cd manca

# create an empty __init__.py file
touch __init__.py

Let’s make a script called index.py inside the main module. The initial endpoint of the application will be defined in this script.

from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello_world():
  return "Hello, World!"

The application just returns a “Hello, world!” message, just as in the previous example. You’ll begin refining it in a moment, but first, let’s create an executable file called boot_strap.sh in the application’s main directory.

# move to the main directory
cd ..

# create the file
touch boot_strap.sh

# make it executable
chmod +x boot_strap.sh

The purpose of this file is to make it easier to start the application. The Source Code for it will be as follows:

#!/bin/sh
export FLASK_APP=./manca/index.py
source $(pipenv --venv)/bin/activate
flask run -h 0.0.0.0

The first command, exactly like when you started the “Hello, world!” application, defines the main script that Flask API will run. The second activates pipenv’s virtual environment, allowing your program to locate and execute its dependencies. Finally, you execute the Flask application, which listens to all computer interfaces (-h 0.0.0.0).

You can now run./boot_strap.sh to see if the script is operating properly. This will get the same result as when you ran the “Hello, world!” application.

# * Serving Flask app "manca.index"
# * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Creating a RESTful Endpoint with Flask

You can start defining some useful endpoints now that you have the application structured. As previously said, the purpose of the program is to assist users in managing their Income and Expenses. You’ll start by defining two endpoints to handle earnings to get the feet wet. Replace the following with the contents of the ./manca/index.py file:

from flask import Flask, jsonify, request
app = Flask(__name__)
incomes = [
  { 'description': 'salary', 'amount': 7000 }
]
@app.route('/incomes')
def get_incomes():
  return jsonify(incomes)
@app.route('/incomes', methods=['POST'])
def add_income():
  incomes.append(request.get_json())
  return '', 204

You need to remove the endpoint that returned “Hello, world!” to users since you are updating the program. You replaced it with an endpoint that responds to HTTP GET queries for incomes and another that responds to HTTP POST requests for new incomes. The @app.route annotation indicates that these endpoints listen for requests on the /incomes endpoint. Flask API has fantastic documentation on what this does.

To make things easier, you can use income as dictionaries right now. You’ll be creating classes to reflect Revenue and Expenses shortly. You can start the application and submit some HTTP requests to interact with both endpoints you’ve created:

# start the manca application
./boot_strap.sh &

# get incomes
curl http://localhost:5000/incomes
# add new income
curl -X POST -H "Content-Type: application/json" -d '{
  "description": "lottery",
  "amount": 1000.0
}' http://localhost:5000/incomes
# check if lottery was added
curl localhost:5000/incomes
Flask API - Flask Endpoint
Image Source

Mapping Models with Python Classes

In a simple use case like the one above, using dictionaries is sufficient. However, you may need to encapsulate the data inside Python classes for more complicated applications that deal with several entities and have multiple business Rules and Validations.

You’ll refactor the application to understand how to map entities (like earnings) to classes. The first step will be to establish a submodule that will house all of the entities. Inside the manca module, make a directory named model and add an empty file called __init__.py to it.

# create model directory inside the manca module
mkdir -p manca/model

# initialize it as a module
touch manca/model/__init__.py

A) Mapping a Python Superclass

You’ll build three classes in this new module/directory: Transaction, Income, and Expense. The first class, Transaction, will serve as the foundation for the other two. Let’s make a transaction.py file in the model directory that has the following code:

import datetime as dt
from marshmallow import Schema, fields
class Transaction():
  def __init__(self, description, amount, type):
    self.description = description
    self.amount = amount
    self.created_at = dt.datetime.now()
    self.type = type
  def __repr__(self):
    return '<Transaction(name={self.description!r})>'.format(self=self)
class TransactionSchema(Schema):
  description = fields.Str()
  amount = fields.Number()
  created_at = fields.Date()
  type = fields.Str()

You defined a Transaction Schema in addition to the Transaction class. The latter will be used to deserialize and serialize Transaction instances from and to JSON objects. This class is descended from the Schema superclass, which is specified in a package that has yet to be installed.

# installing marshmallow as a project dependency
pipenv install marshmallow

Marshmallow is a widely used Python package for converting complicated data types like objects to and from native Python data types. Essentially, this package can be used to validate, serialize, and deserialize data. You won’t go into detail on validation in this post. You will, however, use marshmallows to serialize and deserialize items through Flask APIs, as previously stated.

B) Mapping Income and Expense as Python Classes

You won’t provide the Transaction class on endpoints to keep things more ordered and relevant. To handle the requests, you’ll develop two specializations: Income and Expense. Inside the model module, create a file called income.py containing the following code:

from marshmallow import post_load
from .transaction import Transaction, TransactionSchema
from .transaction_type import TransactionType
class Income(Transaction):
  def __init__(self, description, amount):
    super(Income, self).__init__(description, amount, TransactionType.INCOME)
  def __repr__(self):
    return '<Income(name={self.description!r})>'.format(self=self)


class IncomeSchema(TransactionSchema):
  @post_load
  def make_income(self, data):
    return Income(**data)

The sole benefit of this class to the program is that it hardcodes the transaction type. This type is a Python enumerator that will help filter transactions in the future. You still need to create it. To represent this enumerator, let’s make a new file called transaction_type.py within the model:

from enum import Enum
class TransactionType(Enum):
  INCOME = "INCOME"
  EXPENSE = "EXPENSE"

The enumerator’s code is straightforward. It simply defines a TransactionType class, which derives from Enum and has two types: INCOME and EXPENSE.

Last but not least, make the expenditure class. To do so, create a new file inside the model named expense.py with the following code:

from marshmallow import post_load

from .transaction import Transaction, TransactionSchema
from .transaction_type import TransactionType
class Expense(Transaction):
  def __init__(self, description, amount):
    super(Expense, self).__init__(description, -abs(amount), TransactionType.EXPENSE)
  def __repr__(self):
    return '<Expense(name={self.description!r})>'.format(self=self)
class ExpenseSchema(TransactionSchema):
  @post_load
  def make_expense(self, data):
    return Expense(**data

This class, like Income, hardcodes the transaction type, but it now passes EXPENSE to the superclass. It’s unique in that it requires the amount given to be negative. As a result, regardless of whether the customer sends a positive or negative value, you will record it as negative to make calculations easier.

Serialising and Deserialising Objects with Marshmallow

You can now improve the endpoints to deal with these classes by appropriately implementing the Transaction superclass and its specializations. Now change the contents of./manca/index.py to:

from flask import Flask, jsonify, request
from manca.model.expense import Expense, ExpenseSchema
from manca.model.income import Income, IncomeSchema
from manca.model.transaction_type import TransactionType
app = Flask(__name__)

transactions = [
  Income('Salary', 7000),
  Income('Dividends', 200),
  Expense('pizza', 50),
  Expense('Rock Concert', 100)
]
@app.route('/incomes')
def get_incomes():
  schema = IncomeSchema(many=True)
  incomes = schema.dump(
    filter(lambda t: t.type == TransactionType.INCOME, transactions)
  )
  return jsonify(incomes.data)


@app.route('/incomes', methods=['POST'])
def add_income():
  income = IncomeSchema().load(request.get_json())
  transactions.append(income.data)
  return "", 204
@app.route('/expenses')
def get_expenses():
  schema = ExpenseSchema(many=True)
  expenses = schema.dump(
      filter(lambda t: t.type == TransactionType.EXPENSE, transactions)
  )
  return jsonify(expenses.data)

@app.route('/expenses', methods=['POST'])
def add_expense():
  expense = ExpenseSchema().load(request.get_json())
  transactions.append(expense.data)
  return "", 204
if __name__ == "__main__":
    app.run()

The revised version begins by redefining the income variable as a list of Expenses and Incomes, which are now referred to as transactions. Aside from that, you’ve changed the way both income-related approaches are implemented. You defined an instance of IncomeSchema to build JSON representations of incomes for the Flask API used to get them. Filter was also used to extract only incomes from the transactions list. Finally, you returned the array of JSON earnings to the users.

The endpoint that accepts new incomes has also been refactored. The addition of IncomeSchema to load an instance of Income depending on the JSON data given by the user was the update to this Flask API. You just added the new Income to the transactions list because it interacts with instances of Transaction and its subclasses.

The get expenses and add expenses endpoints, which deal with expenses, are nearly identical to their income counterparts. The differences are: 

  • Instead of dealing with instances of Income, you deal with instances of Expense to accept new expenses, 
  • and instead of using TransactionType as a filter. You filter INCOME by TransactionType. EXPENSE is used to return expenses to the user.

This completes the Flask API implementation. You can now communicate with the endpoints by running the Flask API, as illustrated here:

# start the application
./boot_strap.sh &
# get expenses
curl http://localhost:5000/expenses
# add a new expense
curl -X POST -H "Content-Type: application/json" -d '{
    "amount": 20,
    "description": "lottery ticket"
}' http://localhost:5000/expenses
# get incomes
curl http://localhost:5000/incomes
# add a new income
curl -X POST -H "Content-Type: application/json" -d '{
    "amount": 300.0,
    "description": "loan payment"
}' http://localhost:5000/incomes

Dockerizing Flask Applications

You’ll write a Dockerfile to describe what’s required to run the application on a Docker container because you aim to deploy Flask API on the Cloud. To test and execute dockerized instances of the Flask API, you’ll need to install Docker on the development machine. Creating a Docker recipe (Dockerfile) will enable you to run the Flask API in various contexts. That is, you will install Docker in the future and run the software in environments such as production and staging.

Let’s make a Dockerfile in the project’s root directory with the following code:

# Using lightweight alpine image
FROM python:3.6-alpine
# Installing packages
RUN apk update
RUN pip install --no-cache-dir pipenv
# Defining working directory and adding source code
WORKDIR /usr/src/app
COPY Pipfile Pipfile.lock boot_strap.sh ./
COPY manca ./manca
# Install API dependencies
RUN pipenv install
# Start app
EXPOSE 5000
ENTRYPOINT ["/usr/src/app/boot_strap.sh"]

The first item in the recipe specifies that you will use the default Python 3 Docker image to build the Docker container. After that, you will install pipenv and update APK. You will define the working directory in the image using pipenv, and you can copy the code needed to bootstrap and start the application. Pipenv is used in the fourth stage to install all of the Python dependencies. Finally, you can specify that the image will communicate through port 5000 and that when it is executed, it must run the boot_strap.sh script to launch Flask API.

The following commands can be used to create and run a Docker container based on the Dockerfile that you created:

# build the image
docker build -t manca .
# run a new docker container named manca
docker run --name manca 
    -d -p 5000:5000 
    manca
# fetch incomes from the dockerized instance
curl http://localhost:5000/incomes/

The Dockerfile is straightforward but effective, and it’s simple to use. You can easily run as many instances of The Flask API as you need with these instructions and this Dockerfile. It’s as simple as defining a different port on the host, or even a different host.

Securing Python APIs with Auth0

Securing Python Flask APIs with Auth0 is simple and comes with a lot of useful features. You merely need to write a few lines of code with Auth0 to get:

  • Single Sign-On and User Management are included in this solid identity management solution.
  • Support for providers of Social Identities (like Facebook, GitHub, Twitter, etc.)
  • Identity Management Services for businesses (Active Directory, LDAP, SAML, etc.)
  • Users from your own database.

To secure Python Flask APIs, you can simply add a requires_auth decorator:

# Format error response and append status code

def get_token_auth_header():
    """Obtains the access token from the Authorization Header
    """
    auth = request.headers.get("Authorization", None)
    if not auth:
        raise AuthError({"code": "authorization_header_missing",
                        "description":
                            "Authorization header is expected"}, 401)

    parts = auth.split()

    if parts[0].lower() != "bearer":
        raise AuthError({"code": "invalid_header",
                        "description":
                            "Authorization header must start with"
                            " Bearer"}, 401)
    elif len(parts) == 1:
        raise AuthError({"code": "invalid_header",
                        "description": "Token not found"}, 401)
    elif len(parts) > 2:
        raise AuthError({"code": "invalid_header",
                        "description":
                            "Authorization header must be"
                            " Bearer token"}, 401)

    token = parts[1]
    return token

def requires_auth(f):
    """Determines if the access token is valid
    """
    @wraps(f)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
        jwks = json.loads(jsonurl.read())
        unverified_header = jwt.get_unverified_header(token)
        rsa_key = {}
        for key in jwks["keys"]:
            if key["kid"] == unverified_header["kid"]:
                rsa_key = {
                    "kty": key["kty"],
                    "kid": key["kid"],
                    "use": key["use"],
                    "n": key["n"],
                    "e": key["e"]
                }
        if rsa_key:
            try:
                payload = jwt.decode(
                    token,
                    rsa_key,
                    algorithms=ALGORITHMS,
                    audience=API_AUDIENCE,
                    issuer="https://"+AUTH0_DOMAIN+"/"
                )
            except jwt.ExpiredSignatureError:
                raise AuthError({"code": "token_expired",
                                "description": "token is expired"}, 401)
            except jwt.JWTClaimsError:
                raise AuthError({"code": "invalid_claims",
                                "description":
                                    "incorrect claims,"
                                    "please check the audience and issuer"}, 401)
            except Exception:
                raise AuthError({"code": "invalid_header",
                                "description":
                                    "Unable to parse authentication"
                                    " token."}, 400)

            _app_ctx_stack.top.current_user = payload
            return f(*args, **kwargs)
        raise AuthError({"code": "invalid_header",
                        "description": "Unable to find appropriate key"}, 400)
    return decorated

Then apply it to the endpoints as follows:

# Controllers API

# This doesn't need authentication
@app.route("/ping")
@cross_origin(headers=['Content-Type', 'Authorization'])
def ping():
    return "All good. You don't need to be authenticated to call this"

# This does need authentication
@app.route("/secured/ping")
@cross_origin(headers=['Content-Type', 'Authorization'])
@requires_auth
def secured_ping():
    return "All good. You only get this message if you're authenticated"

Conclusion

You have learned about the core components required to create a well-structured Flask API in this article. You looked at how to utilize pipenv to handle our API’s dependencies. After that, you installed Flask and Marshmallow and used them to create endpoints that could receive and deliver JSON responses. Finally, you looked at how to dockerize the API, which would make it easier to deploy the application to the Cloud. Building Flask APIs manually can be challenging especially for a beginner & this is where Hevo saves the day.

Visit our Website to Explore Hevo

Hevo Data provides its users with a simpler platform for integrating data from 100+ Data Sources such as Python and REST APIs for Analysis. It is a No-code Data Pipeline that can help you combine data from multiple sources. You can use it to transfer data from multiple data sources into your Data Warehouses, Database, or a destination of your choice. It provides you with a consistent and reliable solution to managing data in real-time, ensuring that you always have Analysis-ready data in your desired destination.

Want to take Hevo for a spin? Sign Up for a 14-day free trial and experience the feature-rich Hevo suite first hand. You can also have a look at our unbeatable pricing that will help you choose the right plan for your business needs!

Share your experience of learning about Flask APIs! Let us know in the comments section below!

mm
Former Research Analyst, Hevo Data

Harsh comes with experience in performing research analysis who has a passion for data, software architecture, and writing technical content. He has written more than 100 articles on data integration and infrastructure.

No-code Data Pipeline for Your Data Warehouse