Flask for data science

Share this article

Are you looking for a gentle explanation of what Flask is and why Flask is useful for data science practitioners? Well then you are in the right place! In this article, we provide a beginner-friendly explanation of what Flask is, when Flask should be used by data science practitioners, and how to use Flask. 

This article was written as part of a broader case study on building production-ready machine learning models. That being said, it is also a great standalone resource for someone who is looking for a gentle introduction to Flask.

How to host a machine learning model using Flask

Background reading on web development

Flask is a web development framework so before you can understand how Flask works, you need to understand a few fundamental concepts surrounding web development and how the web works. Here are a few concepts and technologies you should read about if they sound unfamiliar to you. 

  • Localhost and ports. This brief article is great for anyone who has never run a web application on their local computer using localhost. It provides a highilevel overview of what localhost is and how ports are used in conjunction with localhost.  
  • A Gentle introduction to HTTP requests for Flask. This article is great for people who are just starting to learn about HTTP requests and web development. It contains background information on what HTTP requests are, why they are used, and how they are typically structured. 
  • Jinja2 for Flask. This article is perfect for data scientists who have never used a web templating language like Jinja2 before. It explains what Jinja2 is, how it contributes to the Flask ecosystem, and what basic Jinja2 syntax looks like. 

What is flask?

What is Flask? Flask is a lightweight web development framework that makes it easy for developers to build out web applications that can accept and respond to HTTP requests. These web applications can range from simple APIs that accept and respond to data in JSON format to fully functional web pages that users can interact with in their browsers. 

Why use Flask for data science?

Why should you use Flask for your data science projects? Flask is a lightweight web development framework so it is great for situations where you want to make your model accessible on the web so that it can be used by other people and programs. 

Depending on your use case, you might want to surface your model with collaborators within your organization, customers you serve at external organizations, or just any random user on the internet. No matter who you want to share your model with, Flask is a great choice. 

When should you use Flask for data science?

What specific use cases should Flask be used in? Here are the main use cases where you should use Flask in your data science projects. 

  • Build web pages that collect data and surface model predictions. The first case where Flask is useful is if you have built a machine learning model and you want to build a web page that allows a user to interact with that model. For example, the web page may allow users to input data that is then used to make a prediction using that model. As a more specific example, you might build a web page that has a text box where you can enter a tweet. After the tweet is entered, it can then be run through a classification model to determine whether the tweet has a positive or negative sentiment. The sentiment would be calculated and displayed on the web page that appears after you submit the data in the text box. 
  • Build an API to surface model predictions. The next use case is if you want to build a simple API that takes in some data then runs that data through your model to return a prediction. In this case, the final output would be a simple piece of data (integer, string, json, etc.) that represents your prediction rather than a complete web page. Continuing on with the previous example, you might build an API that has an endpoint that accepts a dictionary with a field called “text” that contains a tweet. The tweet would then get through the model to return a dictionary with a field called “prediction” that indicates whether the tweet has a positive or negative sentiment. 

Flask basics: URLs, views, and routes

Now that we have discussed what Flask is and what use cases Flask is used for, we will go into more detail about how to use Flask. We will start by reviewing some basic concepts that are fundamental to Flask.

URLs, routes, and views are all fundamental concepts that you need to be familiar with before you can understand how to use Flask. We will start off by talking about URLs because most people reading this should already be familiar with the concept of a URL. 

A diagram showing how Flask routes, Flask views, and Flask URLs interact

Flask URLs

A unique resource locator, or a URL, is a path that uniquely specifies the location of a resource that is accessible on the web. URLs are most commonly thought of as specifying the location of web pages that you can visit in your browser, but they can also specify the location of other types of resources such as image files. 

All of the following are examples of URLs that specify the location of specific resources on our website.

  • https://crunchingthedata.com
  • https://crunchingthedata.com/http-for-flask/
  • https://crunchingthedata.com/?s=flask
 
So why are URLs important for Flask developers? They are important because every HTTP request that is made using a Flask application must be directed as a specific URL. Only by knowing the URL that a HTTP request is directed at can you understand how the web application should respond to that request.
 
Note that the URLs you use in Flask will be relative to the domain on which your application is running. For example, if you are running a Flask application on localhost at port 5000 and you have created a route for the /predict URL then the full URL you send your request to will be http://localhost:5000/predict.

Flask views

So what is a view? A view is a function that a developer writes that contains the code that determines how a web application should respond to a given HTTP request. In general, each URL will be associated with a specific view function that dictates how to respond to HTTP requests that are directed at that URL. 

Let’s continue along with the example we used earlier about creating an API to serve a model that predicts the sentiment of a tweet. The view function that is associated with the prediction endpoint would read in the data that was provided in the request, run the data through the model, and return a response that contains the predicted sentiment. 

Flask routes

Now that we have discussed what URLs and views are, it will be easy to understand what a route is. In Flask, a route is simply a map from a specific URL to the view function that should handle that URL. 

Flask basics: Templates and static content

If you have ever looked at the code for a Flask application before then you have probably noticed two folders called templates and static within the directory structure. In this section, we will briefly describe what templates and static content are so that you understand what kind of code belongs in these folders. 

Flask static content

What is contained in the static folder in a Flask app? The static folder contains content that is static and does not change depending on the data that is sent in a HTTP request. No matter what data is sent over, the same exact file will always be returned. An example of this is a static CSS style sheet that informs how a page should be formatted. 

Flask templates

What is contained in the templates folder in a Flask app? The templates folder in a Flask application contains dynamic content that changes when different data is sent in a HTTP request. An example of a file that might be stored in the templates folder is an HTML page that displays different content depending on the data that is sent through the HTTP request. 

Flask tutorial

Now that we have gone over the basic building blocks of flask, we will walk through an example of using Flask to surface the results of a model. First we will use Flask to create a web page with some dropdown menus and text boxes that can be used to enter data that is then fed to a machine learning model. After that, we will create an example of an API endpoint that accepts a JSON blob with data that should be fed to the model and returns a JSON blob that contains the model prediction.

We will be using a model that we trained in the previous step of this case study in both of these examples. This model uses demographic data about a customer as well as broader economic variables to predict whether a customer will submit a bank deposit after participating in a marketing campaign.

0. Basic files and directory structure

Before we start writing any code, we will add a few folders to our package directory. Specifically, we will add a folder called templates and a folder called static to the base directory of our package. It is important that these folders have the exact names printed above because Flask automatically looks for folders with those names when certain functions are called. These folders will house the templates and static content that is returned by our application. 

1. Create a basic application with one route

Now that we have set up our directory, the first thing we will do is create a basic Flask application. We will create a new file in the root directory of our Python package called app.py and instantiate the Flask application within this file. The class takes the name of the current module as an argument so that it can keep track of where it was instantiated.

from flask import Flask
app = Flask(__name__)

After we instantiate our app, we will create a simple view function that returns the string “Hello world!” when requests are made. We will call this view function index because we will use it to manage the index URL, or the base URL at the root of our web application.

 
from flask import Flask

app = Flask(__name__)

def index():
  return "Hello world!"

After we create our view function, we need to set up a route to tell Flask which URL the view should manage. We will use the @app.route decorator to set up the route and pass the string “/” as a parameter to indicate that this view should manage the index URL.

After we set up this route, we will add some code to the the bottom of our file to ensure that the application only gets instantiated when we want it to. We can do this by checking whether the current file is the main file that the python code is being run from

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
  return "Hello world!"

if __name__ == '__main__':
  app.run(debug=True)

Now we have a fully functional Flask application with one route. Let’s get our application up and running. All you have to do to get your web application up and running on your local computer is navigate to the base directory of your repository and type the following command into your terminal.  

python3 pkgs/bank_deposit_classifier/app.py

Once your application is up and running, you can open a web browser and navigate to the address http://localhost:5000 to see your web application in action. When you go to that address, you should see the hello world message that your view returns printed out on the top of the screen. 

2. Rendering a template in a view

But what if we want to do more than just display a string on the index page of our application? What if we want to display a proper HTML-formatted web page instead? In the next step we will use some basic HTML code to sketch out a template for a web page that we want our view function to return. 

We can use the code below to create a basic web page that has a dropdown menu that lets you select a customer’s occupation. Occupation is one of the variables that is used in our predictive model, so we want to be able to collect a customer’s occupation to feed it to the model. Will save this code to a file called index.html in the templates folder we previously created. 

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <label for="career">Choose a career:</label>
  <select name="career" id="career">
    <option value="job_blue_collar">Blue collar</option>
    <option value="job_housemaid">Housemaid</option>
    <option value="job_management">Managment</option>
    <option value="job_services">Services</option>
    <option value="job_technician">Technician</option>
    <option value="job_self_employed">Self Employed</option>
    <option value="job_entrepreneur">Entrepreneur</option>
    <option value="job_student">Student</option>
    <option value="job_retired">Retired</option>
    <option value="job_unemployed">Unemployed</option>
    <option value="job_unknown">Unknown</option>
  </select>

</body>
</html>

We will also need to make some changes to our view function so that it returns a proper web page. All you need to do in order to do this is import the render_template function from Flask and call the render_template function in the return statement of your view function. Now when you look at your web application on localhost, you should see a dropdown menu that prompts you to choose a career.

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

if __name__ == '__main__':
app.run(debug=True)

3. Template inheritance with Jinja2

If you look at the HTML code that we wrote out in the previous step, you will see that a lot of the code we wrote was actually just boilerplate code that is required to set up a HTML page rather than unique code that is specific to that page. This boilerplate code is shared code that will need to be repeated across all of the pages that we create in our Flask application.

Wouldn’t it be nice if we could take all of that boilerplate code and put it in a single file that our other pages can inherit from? That is exactly what Jinja2 allows us to do with template inheritance! 

In order to create a template with the boilerplate code that will be repeated across our web pages, we will create a new file called base.html and place it in our templates directory. We will copy the content from our index.html file to the base.html file and replace the unique code that is specific to our index.html page with the following Jinja2 placeholder.

{% block body %}{% endblock%}

What does this Jinja2 code do? The code within the first set of parentheses uses the keyword block to specify the start of a Jinja2 placeholder with the name body. The second set of parentheses includes the keyword endblock to indicate the end of the Jinja2 placeholder. This is what our base.html file should look like when we are done.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  {% block body %}{% endblock %}
</body>
</html>

After we create our base.html file, we need to update our index.html file to inherit from our base.html template. First, we need to specify which template index.html should inherit from by creating another Jinja2 block with the extends keyword and the name of the file we want to inherit from.

After that, we should copy the Jinja2 placeholder bookends from the file we are inheriting from and put the unique code that is specific to this page between the bookends. Here is what your index.html file should look like when you are done. 

{% extends 'base.html' %}

{% block body %}
  <label for="career">Choose a career:</label>
  <select name="career" id="career">
    <option value="job_blue_collar">Blue collar</option>
    <option value="job_housemaid">Housemaid</option>
    <option value="job_management">Managment</option>
    <option value="job_services">Services</option>
    <option value="job_technician">Technician</option>
    <option value="job_self_employed">Self Employed</option>
    <option value="job_entrepreneur">Entrepreneur</option>
    <option value="job_student">Student</option>
    <option value="job_retired">Retired</option>
    <option value="job_unemployed">Unemployed</option>
    <option value="job_unknown">Unknown</option>
  </select>
{% endblock %}

4. Adding forms that accept data

So now we have a nice dropdown menu that allows us to select a career on our index page. But what if we want to be able to grab a user’s selection from the dropdown menu and use that data in a predictive model? In order to be able to grab this data via a HTTP request, we will need to wrap the dropdown menu in a form that collects data.

All we have to do to wrap our dropdown menu in a form is place a form tag with the appropriate method before our dropdown menu and a /form tag after our dropdown menu. When we make a HTTP request using this data, we will be sending the data to the server so that it can be run through the model. Since we are sending resources to the server, we will use the POST method for this request.

{% extends 'base.html' %}

{% block body %}

<form method="POST"><label for="career">Choose a career:</label>
<select id="career" name="career">
<option value="job_blue_collar">Blue collar</option>
<option value="job_housemaid">Housemaid</option>
<option value="job_management">Managment</option>
<option value="job_services">Services</option>
<option value="job_technician">Technician</option>
<option value="job_self_employed">Self Employed</option>
<option value="job_entrepreneur">Entrepreneur</option>
<option value="job_student">Student</option>
<option value="job_retired">Retired</option>
<option value="job_unemployed">Unemployed</option>
<option value="job_unknown">Unknown</option>
</select>
<input type="submit" value="Predict" /></form>{% endblock %}

We will also need to make some changes to our index view function so that it can accept the data from our form. Specifically, we will have to make sure that our index view functions can handle POST request in addition to GET requests. 

Taking a step back, if you do not provide the specific method you are interested in using to the @app.route decorator, it defaults to using a GET request. Falling back on this default behavior made sense before because we just needed to grab a web page from the server when we navigated to our index URL. Now we need to be able to handle both GET requests and POST requests through our index URL, so we will need to provide the @app.route decorator with the list of methods we want to accept. 

Additionally, we will need to add some logic to our index view function to inform how the function behaves for POST requests and GET requests. We can do this by importing request from Flask and using request.method to determine what the method of the request that was sent through was. 

There will be no change in behavior when a GET method is called. However, when a POST method is called, we will use request.form to grab the data that was passed in the form we set up and use that data in our response. This is what our app.py file will look like when we are done. 

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        career = request.form['career']
        return f'Prediction for {career} is NOT_IMPLEMENTED'
    else:
        return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

Now you should see a form with a submit button under the dropdown menu with the different career options. You can press this submit button to make a POST request that sends the data collected in the form to the server.

5. Surfacing model predictions on a web page

Now is the time that we have all been waiting for! In this step, we will incorporate our predictive model into our web application so that we can use our model to make predictions using the data that is provided in our form.

There is one slight hiccup that we might encounter here. Specifically, the issue is that we are running our Flask application on port 5000 of localhost but we also need to load our model via the MLflow server. We were previously running the MLflow model server on port 5000 of localhost as well. We will need to change the port that one of these applications is running on so that we can run both of them at the same time. 

All we need to do in order to change the port that our Flask application is running on is add a port argument to the app.run method in our app.py file and provide a different port number. After that, we will stop the Flask application and restart the application using the same command we used before. 

python3 pkgs/bank_deposit_classifier/app.py

After we get our Flask application running on a different port, we will get the MLflow server up and running as we did in the previous step of this case study so that we can load your model. 

mlflow server --backend-store-uri sqlite:///mlflow.db --default-artifact-root ./mlflow-artifact

Now that everything is all set up, we will add a new file to our package that loads our model and makes a prediction. We will call this file predict.py, but you should feel free to give it any name your please. Below are the contents of our predict.py file. 

Since we are only collecting the value associated with one of our variables in our form, we will need to make sure that we provide default values for the rest of our variables. Most of the code below sets up the default values for our variables and ensures that all of the variables are being fed to the predict function in the correct order. 

At the end of our predict.py file, we define two functions that will need to be used in our web application. The first is a load_model function that loads our model from MLflow. The second is a predict_with_defaults function that grabs the default values of the data, updates the default values with values that were provided, and makes a prediction using our MLflow model. 

import mlflow
import pandas as pd

COLUMN_ORDER = [
  'job_blue_collar',
  'job_entrepreneur',
  'job_housemaid',
  'job_management',
  'job_retired',
  'job_self_employed',
  'job_services',
  'job_student',
  'job_technician',
  'job_unemployed',
  'job_unknown',
  'marital_married',
  'marital_single',
  'marital_unknown',
  'education_basic_6y',
  'education_basic_9y',
  'education_high_school',
  'education_illiterate',
  'education_professional_course',
  'education_university_degree',
  'education_unknown',
  'default_unknown',
  'default_yes',
  'housing_unknown',
  'housing_yes',
  'loan_unknown',
  'loan_yes',
  'contact_telephone',
  'month_aug',
  'month_dec',
  'month_jul',
  'month_jun',
  'month_mar',
  'month_may',
  'month_nov',
  'month_oct',
  'month_sep',
  'day_of_week_mon',
  'day_of_week_thu',
  'day_of_week_tue',
  'day_of_week_wed',
  'poutcome_nonexistent',
  'poutcome_success',
  'age',
  'duration',
  'campaign',
  'pdays',
  'previous',
  'emp_var_rate',
  'cons_price_idx',
  'cons_conf_idx',
  'euribor3m',
  'nr_employed'
]
NUMERIC_COLUMNS = [
  'age', 
  'duration', 
  'campaign', 
  'pdays', 
  'previous', 
  'emp_var_rate',
  'cons_price_idx', 
  'cons_conf_idx', 
  'euribor3m', 
  'nr_employed'
]
CATEGORICAL_COLUMNS = [
  x for x in COLUMN_ORDER 
  if x not in NUMERIC_COLUMNS
]
COLUMN_DEFAULTS = {
  x: [0] 
  for x in COLUMN_ORDER
}
DEFAULT_DATA = pd.DataFrame(COLUMN_DEFAULTS)[COLUMN_ORDER]

def load_model(model='bank-deposit-classifier-test', version='1'):
  mlflow.set_tracking_uri('http://localhost:5000')
  path = f'models:/{model}/{version}'
  model =   mlflow.sklearn.load_model(path)
  return model

def predict_with_defaults(model, categoricals=[], numerics={}):
  data = DEFAULT_DATA.copy()
  for c in categoricals:
    data = 1
  for k, v in numerics.items():
    data[k] = v
  prediction = model.predict(data)[0]
  return prediction

After we create our predict.py file, we will need to import the functions that are used to load our model and make predictions so that we can use them in our app.py file. All we have to do now is update the code in our index view function to make predictions with our model. 

from flask import Flask, render_template, request
from bank_deposit_classifier.predict import (load_model, predict_with_defaults,
  CATEGORICAL_COLUMNS, NUMERIC_COLUMNS)


app = Flask(__name__)
model = load_model()

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        categoicals = [x for x in request.form.values() if x in CATEGORICAL_COLUMNS]
        numerics = {x:request.form.get(x) for x in request.form if x in NUMERIC_COLUMNS}
        prediction = predict_with_defaults(
            model,
            categoricals=categoicals,
            numerics=numerics
            )
        prediction_ = 'WILL' if prediction == 1 else 'WILL NOT'
        return f'We predict the customer {prediction_} submit a deposit'

    else:
        return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, port=3000)

Now if you navigate to the index page of your web application and press the submit button, you should be able to see your models predictions written across the page. 

6. Surfacing model predictions in an API endpoint

So what if your end goal is not to create a web page that a user can interact with in their web page? What if instead you want to create an API endpoint that a user can send and receive data from programmatically? In this final section, we will add a new view function and route to our app.py file to enable this type of API calls.

Specifically, we will create a new URL called /predict that accepts POST requests that contain model features and respond with a model prediction. We will not spend much time going over this code because most of it is not Flask specific. Rather, it is simple python code that unpacks data from a dictionary that contains an entry called features with a dictionary with feature names as keys and feature names as values. 

import json

from flask import Flask, render_template, request

from bank_deposit_classifier.predict import (load_model, predict_with_defaults,
  CATEGORICAL_COLUMNS, NUMERIC_COLUMNS)


app = Flask(__name__)
model = load_model()

# TODO: move this parsing inside predict
def sort_features_by_type(features):
    categoicals = [v for k, v in features.items() if v in CATEGORICAL_COLUMNS]
    numerics = {k:v for k, v in features.items() if k in NUMERIC_COLUMNS}
    return {'numeric_features': numerics, 'categorical_features': categoicals}

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        features_ = sort_features_by_type(request.form)
        prediction = predict_with_defaults(
            model,
            categoricals=features_.get('categorical_features'),
            numerics=features_.get('numeric_features')
            )
        prediction_ = 'WILL' if prediction == 1 else 'WILL NOT'
        return f'We predict the customer {prediction_} submit a deposit'

    else:
        return render_template('index.html')

@app.route('/predict', methods=['POST'])
def predict():
    try:
        data = request.get_json(force=True)
        features = data.get('features')
        features_ = sort_features_by_type(features)
    except:
        return 'Could not unpack features'

    prediction = predict_with_defaults(
        model,
        categoricals=features_.get('categorical_features'),
        numerics=features_.get('numeric_features')
        )
    prediction_ = 'WILL' if prediction == 1 else 'WILL NOT'
    return {
        'prediction': prediction,
        'prediction_text': prediction_,
        'categorical_features': features_.get('categorical_features'),
        'numeric_features': features_.get('numeric_features')
        }


if __name__ == '__main__':
    app.run(debug=True, port=3000)

Once you add this code, you can use the requests model in python to send a POST request to the URL that is managed by your view function. 

import requests

url = 'http://localhost:3000/predict'
r = requests.post(url, json={'features': {'age': 62}})
print(r.json())

Learn more about Flask

This Flask tutorial is intended to be a relatively simple tutorial that gets you familiar with the basics. Here are some other concepts that you should read about to get more familiar with Flask. 

  • Adding a database. Do you need to add a database to your Flask application so that you can store data that is sent in POST requests? Check out this documentation on adding a database to your Flask app. 
  • Using Flask blueprints. Do you have many related views that you want to be able to organize and group together? Check out this documentation on using Flask blueprints.  

Share this article

Leave a Comment

Your email address will not be published. Required fields are marked *