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
Now that we’ve created a Dash app and integrated it with Flask, let’s work on the layout.py
file, which for now will just contain a Bootstrap navigation bar inside a Bootstrap container:
# /app/dashapp/layout.py
import datetime
import os
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
from flask import url_for
# We'll uncomment these and work on them in a future chapter!
# from app.dashapp.utils import (
# get_stock_industries,
# get_stock_tickers,
# get_time_series_chart,
# ml_features_map,
# ml_models_map,
# )
def get_navbar():
"""Get a Bootstrap 4 navigation bar for our single-page application's HTML layout"""
return dbc.NavbarSimple(
children=[
dbc.NavItem(dbc.NavLink("Blog", href="https://mccarthysean.dev")),
dbc.NavItem(dbc.NavLink("IJACK", href="https://myijack.com")),
dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem("References", header=True),
dbc.DropdownMenuItem("Dash", href="https://dash.plotly.com/"),
dbc.DropdownMenuItem(
"Dash Bootstrap Components",
href="https://dash-bootstrap-components.opensource.faculty.ai/",
),
dbc.DropdownMenuItem("Testdriven", href="https://testdriven.io/"),
],
nav=True,
in_navbar=True,
label="Links",
),
dbc.NavItem(
dbc.Button(
"Logout",
href="/logout/",
external_link=True,
color="primary",
className="ml-2",
)
),
],
brand="Home",
brand_href="/",
color="dark",
dark=True,
)
def get_layout():
"""Function to get Dash's "HTML" layout"""
# A Bootstrap 4 container holds the rest of the layout
return dbc.Container(
[
# Just the navigation bar at the top for now... More to come!
get_navbar(),
],
)
If you recall, the get_layout()
function is called from our dash_setup.py
module, like this:
...
with app.app_context():
# Assign the get_layout function without calling it yet
dashapp.layout = get_layout
I won’t explain the get_navbar()
function because I think it’s self-explanatory. When you see it in the browser, you’ll understand everything. But here’s the documentation if you’re curious.
For the rest of Part 2, we’ll bounce back and forth between the layout.py
file, and the callbacks.py
file. We’ll add a piece to the layout, and then write a callback to interact with it. Rinse and repeat.
That said, let’s get started on our callbacks.py
file beside the layout.py
file. For now, we’ll just import our packages and frame up the register_callbacks
function:
# /app/dashapp/callbacks.py
import json
import os
import time
from io import StringIO
import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import numpy as np
import pandas as pd
import plotly.graph_objs as go
import psycopg2
import yfinance as yf
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask import current_app
from psycopg2.extras import RealDictCursor
# We'll uncomment these later!
# from app.dashapp.ml import feature_engineering, grid_search_cross_validate, train_models
# from app.dashapp.utils import (
# download_prices,
# get_chart,
# get_stock_industries,
# get_stock_price_data_from_db,
# get_stock_tickers,
# get_time_series_chart,
# insert_tickers,
# make_annotations,
# ml_models_map,
# upload_to_aws_efficiently,
# )
# from app.database import get_conn, run_sql_query
def register_callbacks(dash_app):
"""Register the callback functions for the Dash app, within the Flask app"""
pass
Let’s add a few more HTML components to the “body” of the layout.py
file. Currently it’s just a navigation bar at the top, so let’s work our way down by adding a get_top_stock_selection_row
function.
# /app/dashapp/layout.py
def get_top_stock_selection_row(industries, industry): # NEW
"""Get the first row of the layout"""
# The layout starts with a Bootstrap row, containing a Bootstrap column
return dbc.Row(
[
dbc.Col(
[
html.H4("Pick an Industry", style={"margin-top": "1rem"}),
dcc.Dropdown(
options=industries,
value=industry,
id="industries_dropdown",
),
],
xs=12,
sm=6,
md=4,
),
],
style={"margin-top": "1em"},
)
def get_layout():
"""Function to get Dash's "HTML" layout"""
# A Bootstrap 4 container holds the rest of the layout
return dbc.Container(
[
get_navbar(),
get_top_stock_selection_row(industries, industry), # NEW
],
)
Notice in the get_layout()
function, we’ve added get_top_stock_selection_row(industries, industry)
below the navbar function. We’ll deal with the industries
and industry
arguments pretty soon, but for now let’s check out the actual get_top_stock_selection_row
function so we know why we need them.
The function starts by returning a Dash Bootstrap Components (DBC) Row
, inside of which is a Column
. This is standard Bootstrap grid stuff.
# The layout starts with a Bootstrap row, containing a Bootstrap column
return dbc.Row(
[
dbc.Col(
[
Notice also that the get_layout
function first returns a dbc.Container
. At its most basic, for example, a DBC Bootstrap grid would look like the following:
dbc.Container(
dbc.Row(
dbc.Col(
"Some random text"
)
)
)
But since we’re going to be adding more than one row to the container, and more than one column to each row, the first argument is usually a list, more like the following:
dbc.Container(
[
dbc.Row(
[
dbc.Col(
[
"Some random text",
"More random text",
]
),
]
),
]
)
Now, let’s look at what’s inside that first column.
First, there’s a “Heading 4” (heading of size 4 in HTML), created with Dash HTML Components as html.H4
.
After the heading, we’ve got a Dash Core Components (DCC) dropdown menu, whose HTML “ID” is industries_dropdown
. That’s how the callback will refer to it–by its ID.
You’re creating Bootstrap HTML/CSS/JS with nothing but Python! That’s the beauty of Dash.
Finally, note the xs
, sm
, and md
options. On an extra small phone, the column will take up all 12 of the Bootstrap grid columns. On a “small” (sm
) device like an iPad, the column will take up only the first 6 columns, leaving room for another column to take up the other six column spaces. And on a “medium” (md
) sized screen, there’s room for three columns, each taking up 4 columns of space.
def get_top_stock_selection_row(industries, industry): # NEW
"""Get the first row of the layout"""
# The layout starts with a Bootstrap row, containing a Bootstrap column
return dbc.Row(
[
dbc.Col(
[
html.H4("Pick an Industry", style={"margin-top": "1rem"}),
dcc.Dropdown(
options=industries,
value=industry,
id="industries_dropdown",
),
],
xs=12,
sm=6,
md=4,
),
],
style={"margin-top": "1em"},
)
Next, we need to add the industries
and industry
arguments for the dropdown menu. Create a utils.py
file in the dashapp
folder, and add the following code so we can query our TimescaleDB database for the industries available.
# /app/dashapp/utils.py
import datetime
import os
from io import StringIO
import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
import psycopg2
import yfinance as yf
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask import current_app
from psycopg2.extras import RealDictCursor
from app.database import get_conn, run_sql_query
def get_stock_industries():
"""Get a list of different industries for which we have stock prices"""
sql = """
--Get the labels and underlying values for the dropdown menu "children"
SELECT
distinct
industry as label,
industry as value
FROM public.stock_tickers;
"""
rows, _ = run_sql_query(sql)
if len(rows) == 0:
first_value = None
else:
first_value = rows[0]["value"]
return rows, first_value
The get_stock_industries
function does just what you’d expect, querying our TimescaleDB database with regular SQL, and returning the distinct/unique industries in a list of dictionaries, where each row is a dictionary.
Now we need to add the helpful run_sql_query
function to our database.py
module, as follows:
# database.py
def run_sql_query(sql, conn=None):
"""Run a generic query and return the rows and columns"""
if conn is None:
conn = get_conn()
# Use the RealDictCursor cursor factory, so each row returned is a dictionary
with conn.cursor(cursor_factory=RealDictCursor) as cursor:
cursor.execute(sql)
columns = [str.lower(x[0]) for x in cursor.description]
rows = cursor.fetchall()
return rows, columns
Now we can add the get_stock_industries
function to the get_layout
function, and pass its return values to the get_top_stock_selection_row
function, as follows:
def get_layout():
"""Function to get Dash's "HTML" layout"""
industries, industry = get_stock_industries() # NEW
# A Bootstrap 4 container holds the rest of the layout
return dbc.Container(
[
get_navbar(),
get_top_stock_selection_row(industries, industry),
]
)
Uncomment the following import at the top:
from app.dashapp.utils import (
get_stock_industries,
# get_stock_tickers,
# get_time_series_chart,
# ml_features_map,
# ml_models_map,
)
Many Pythonistas enjoy querying their databases with SQLAlchemy, and I use it too, but sometimes I just wanna get dirty and write some SQL like the olden-days. The psycopg2 library lets me do that very nicely. Both are great libraries–well-maintained and battle-tested.
Take this opportunity to check out your new Dash site in your browser. Type docker-compose up -d --build
in your console.
In the next chapter, we’ll add second and third columns to the first row, and use Dash callbacks to populate them, based on the industry chosen in the first dropdown.
Next: Dash Callbacks Setup
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