Course Table of Contents
- Course Repository
- Course Introduction
- Part 1 - Docker, TimescaleDB, and Flask
- Part 2 - Dash
- Part 3 - Machine Learning
- Part 4 - Machine Learning Model in Dash
- Part 5 - Testing and Backups
- Part 6 - Deployment
In this chapter, we’ll fire up a Flask web app for hosting our stock price forecasting, machine learning model.
Flask vs Dash
Flask is a popular Python web framework. It’s lightweight and extensible, so it’s very well-suited for data science applications. Dash is a web framework built on top of Flask, which uses React behind-the-scenes to create reactive, interactive Single-page Applications (SPAs) featuring Plotly charts.
I would say Dash is to Python as Shiny is to R, in that both focus on productionalizing data science and machine learning models, without a user having to learn much HTML, CSS, or JavaScript. Typically data scientists are not software engineers, and the intricacies of making a single-page web application are far too complicated and not the best use of their time.
This tutorial shows you how to get the best of a few different worlds:
- A Flask application for your normal website
- A fancy Dash Single-page Application that employs the best of React and JavaScript
- A way to productionalize your data science application
Let’s start with a simple Flask web application, and I’ll show you how to integrate Dash. Part 2 of this course will dive deeper into making interactive charts in Dash.
Flask Setup
To create a Flask application, let’s start from the outermost entrypoint, or the starting point for the application. In your top-level folder, create a wsgi.py file as follows. This is best practice using the factory pattern for initializing Flask.
# wsgi.py
from app import create_app
app = create_app()
The wsgi.py file is looking in a folder called “app” for a function called create_app
, so let’s create an “app” folder to house our Flask application. Inside the app folder, as for all Python packages, create an __init__.py file:
# app/__init__.py
import logging
import os
# Third-party imports
from flask import Flask, render_template
from dotenv import load_dotenv
# Local imports
from app import database
# Load the secret environment variables using python-dotenv
# Flask actually does this automatically if python-dotenv is installed,
# but we're making it explicit so you know how the environment variables are loaded
load_dotenv()
def create_app():
"""Factory function that creates the Flask app"""
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
logging.basicConfig(level=logging.DEBUG)
@app.route("/")
def home():
"""Our only non-Dash route, to demonstrate that Flask can be used normally"""
return render_template("index.html")
# Initialize extensions
database.init_app(app) # PostgreSQL db with psycopg2
return app
The above file contains the create_app()
factory function that the previous wsgi.py file needs. Ignore the local imports for now – we’ll get back to those.
Inside the create_app()
factory function, we start off with the basics, instantiating the Flask()
instance by passing it the __name__
of the file and setting the SECRET_KEY
… secret key?
def create_app():
"""Factory function that creates the Flask app"""
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
Open the .env file and add a SECRET_KEY
environment variable to the bottom of the file:
# .env
# For the Postgres/TimescaleDB database.
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_HOST=timescale
POSTGRES_PORT=5432
POSTGRES_DB=postgres
PGDATA=/var/lib/postgresql/data
# For the PGAdmin web app
PGADMIN_DEFAULT_EMAIL=your@email.com
PGADMIN_DEFAULT_PASSWORD=password
# For Flask
SECRET_KEY=long-random-string-of-characters-numbers-etc-must-be-unique # NEW
Back to our __init__.py file, we set up the basic logging config and then added our first Flask “route” (the main homepage), just to demonstrate that we’ve got a normal, working Flask application.
def create_app():
"""Factory function that creates the Flask app"""
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
logging.basicConfig(level=logging.DEBUG)
@app.route("/")
def home():
"""Our only non-Dash route, to demonstrate that Flask can be used normally"""
return render_template("index.html")
Create a new folder in “app” called “templates” for our HTML templates, and add an index.html file with the following contents for the homepage route:
<html>
<body>
<h1 style="text-align: center;">
Click <a href="/dash/">here</a> to see the Dash single-page application (SPA)
</h1>
</body>
</html>
Next up in __init__.py, we initialized our database with database.init_app(app)
:
# Local imports
from app import database # we're dealing with this import now
# Load the secret environment variables using python-dotenv
# Flask actually does this automatically if python-dotenv is installed,
# but we're making it explicit so you know how the environment variables are loaded
load_dotenv()
def create_app():
"""Factory function that creates the Flask app"""
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
logging.basicConfig(level=logging.DEBUG)
@app.route("/")
def home():
"""Our only non-Dash route, to demonstrate that Flask can be used normally"""
return render_template("index.html")
# Initialize extensions
database.init_app(app) # PostgreSQL db with psycopg2
“database” is a local module we imported at the top, so we’ll deal with that next.
Database
Let’s create the database.py module next, beside the __init__.py file, following Flask’s recommended best-practices:
# database.py
import os
import psycopg2
from flask import g
def get_conn():
"""
Connect to the application"s configured database. The connection
is unique for each request and will be reused if this is called
again.
"""
if "conn" not in g:
g.conn = psycopg2.connect(
host=os.getenv("POSTGRES_HOST"),
port=os.getenv("POSTGRES_PORT"),
dbname=os.getenv("POSTGRES_DB"),
user=os.getenv("POSTGRES_USER"),
password=os.getenv("POSTGRES_PASSWORD"),
connect_timeout=5
)
return g.conn
def close_db(e=None):
"""
If this request connected to the database, close the
connection.
"""
conn = g.pop("conn", None)
if conn is not None:
conn.close()
return None
def init_app(app):
"""
Register database functions with the Flask app. This is called by
the application factory.
"""
app.teardown_appcontext(close_db)
The point of this database.py module is to make some functions for getting and closing a TimescaleDB database connection and ensuring that the connection is closed by Flask at the end of the HTTP request. The init_app(app)
function is what gets called from our create_app()
factory function. Notice the teardown_appcontext(close_db)
that ensures the connection is closed on “teardown”. Pretty slick. In the future, when we need data from the database, we’ll just call get_conn()
to get a database connection for running our SQL.
In case you’re wondering, g
is basically a global object where you store your database connection. It’s complicated so I won’t get into it – just know this is best-practice and enjoy your life. ;) Don’t believe me? Okay fine. Here’s a a few links for further reading:
If you’re looking for great course on Flask, including a deep-dive into Flask’s mechanics, I highly recommend this course by Patrick Kennedy.
Before we run our Flask app for the first time, let’s create a .flaskenv file beside the .env file, which Flask automatically searches for find when the app starts:
# .flaskenv
FLASK_APP=wsgi:app
FLASK_RUN_HOST=0.0.0.0
To run our Flask app for the first time, recall that our docker-compose.yml file in our “.devcontainer” folder contains our Python container, which looks something like the following. Note that our dev_container
is going to forward all outside requests from port 5006 to port 5000 inside the container.
# Our Python development container, for running our Flask/Dash app, and our Jupyter Notebooks
dev_container:
build:
# context: where should docker-compose look for the Dockerfile?
# i.e. either a path to a directory containing a Dockerfile, or a url to a git repository
context: ..
dockerfile: Dockerfile.dev
env_file:
- ../.env
environment:
FLASK_CONFIG: development
FLASK_ENV: development
# Forwards port 0.0.0.0:5006 from the Docker host (e.g. your computer)
# to the dev environment container's port 5000
ports:
- 0.0.0.0:5006:5000
Recall also that our launch.json file has the following configuration, for launching Flask apps in debugging mode just by pressing “F5”:
// Debug with Flask
{
"name": "flask run --no-debugger --no-reload",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "wsgi:app",
"FLASK_ENV": "development",
"FLASK_DEBUG": "0"
},
"args": [
"run",
"--no-debugger",
"--no-reload",
],
"jinja": true,
"justMyCode": false
}
So to launch our Flask app, just go to the “Run and Debug” menu on the left side of VS Code, select the “flask run –no-debugger –no-reload” configuration, and either press the “play” button, or press “F5”. Once done, you should see our very simple landing page in your browser at http://localhost:5006 or http://127.0.0.1:5006.
Conclusion
Now that we’ve set up a basic Flask web app, let’s add a few other Flask essentials:
- Flask-SQLAlchemy for working with our database
- Registration / login pages for controlling access to our site
See you in the next chapter.
Next: Flask-SQLAlchemy Models
Course Table of Contents
- Course Repository
- Course Introduction
- Part 1 - Docker, TimescaleDB, and Flask
- Part 2 - Dash
- Part 3 - Machine Learning
- Part 4 - Machine Learning Model in Dash
- Part 5 - Testing and Backups
- Part 6 - Deployment
Comments