An example dashboard can be found at github.com/oegedijk/dash_oop_demo and has been deployed to https://dash-oop-demo.herokuapp.com/
First define the DashFigureFactory
and DashComponents
in dashboard.components.py
. In this case I named them CovidPlots
and CovidDashboard
.
Then build the dashboard and save to dashboard.yaml
:
build_dashboard.py:
from dashboard_components import CovidPlots, CovidDashboard
from dash_bootstrap_components.themes import FLATLY
from dash_oop_components import DashApp
plot_factory = CovidPlots(datafile="covid.csv")
dashboard_component = CovidDashboard(plot_factory)
db = DashApp(dashboard_component, querystrings=True, bootstrap=FLATLY)
db.to_yaml("dashboard.yaml")
Then define a dashboard.py
that builds the dashboard from config, and exposes the Flask server:
dashboard.py
from dash_oop_components import DashApp
db = DashApp.from_yaml("dashboard.yaml")
app = db.app.server
And start the gunicorn server:
$ gunicorn --preload -b localhost:8050 dashboard:app
Alternatively you can ofcourse simply use the dashapp
CLI for starting the dashboard outside production environments:
$ dashapp dashboard.yaml
You can also automatically restart the gunicorn server whenever there is a change to dashboard.yaml
, by using watchdog
. Install with pip install watchdog[watchmedo]
. Start the gunicorn server while saving it's pid:
$ gunicorn --pid gunicorn.pid --preload -b localhost:8050 dashboard:app
And the start a watchmedo script that runs kill -HUP
on the gunicorn server in order to force a restart whenever it detects a change to dashboard.yaml
:
$ watchmedo shell-command -p "./dashboard.yaml" -c 'kill -HUP $(cat gunicorn.pid)'
Loading with pickled DashFigureFactory
In some cases you might be doing some expensive calculations inside your DashFigureFactory
that you do not want to run everytime you restart a dashboard as it would break the gunicorn timeout window. The solution is to build the plot_factory, indicate the filepath
of the dumped pickle file, dump it, and then load it from the pickle file when you start the dashboard, by passing try_pickles=True
:
build_dashboard.py:
from dashboard_components import CovidPlots, CovidDashboard
from dash_bootstrap_components.themes import FLATLY
from dash_oop_components import DashApp
plot_factory = CovidPlots(datafile="covid.csv", filepath="plot_factory.pkl")
plot_factory.dump() # stores to plot_factory.pkl
dashboard_component = CovidDashboard(plot_factory)
db = DashApp(dashboard_component, bootstrap=FLATLY)
db.to_yaml("dashboard.yaml")
When you pass try_pickles=True
if the filepath
cannot be found, then the DashPlotFactory
will get rebuilt from
config. If you pass force_pickles=True
, then DashApp
will raise an exception instead:
dashboard.py:
from dash_oop_components import DashFigureFactory, DashComponent, DashApp
db = DashApp.from_yaml("dashboard.yaml", try_pickles=True)
app = db.app.server
We can also you watchmedo to rebuild the plot_factory
whenever there is a change to plot_factory.yaml
or covid.csv
,
and then rebuild the dashboard whenever there is a change to plot_factory.pkl
, dashboard_component.yaml
or dashboard.yaml
:
build_plot_factory.py:
from dash_oop_component import DashFigureFactory
plot_factory = DashFigureFactory.from_yaml("plot_factory.yaml", filepath="plot_factory.pkl")
plot_factory.dump("")
build_dashboard.py:
from dashboard_components import CovidPlots, CovidDashboard
from dash_bootstrap_components.themes import FLATLY
from dash_oop_components import DashApp
plot_factory = DashFigureFactory.from_file("plot_factory.pkl")
dashboard_component = CovidDashboard(plot_factory)
db = DashApp(dashboard_component, bootstrap=FLATLY)
db.to_yaml("dashboard.yaml")
dashboard.py:
from dash_oop_components import DashFigureFactory, DashComponent, DashApp
db = DashApp.from_yaml("dashboard.yaml", try_pickles=True)
app = db.app.server
Now start the gunicorn server with:
$ gunicorn --pid gunicorn.pid --preload -b localhost:8050 dashboard:app
Now run the build_plot_factory.py
script everytime you detect a change in either covid.csv
or plot_factory.yaml
:
$ watchmedo shell-command -p "./covid.csv;./plot_factory.yaml" -c 'python build_plot_factory.py'
Now run the build_dashboard.py
script everytime you detect a change in either plot_factory.pkl
or plot_factory.yaml
:
$ watchmedo shell-command -p "./plot_factory.pkl;./plot_factory.yaml" -c 'python build_dashboard.py'
And restart the gunicorn server everytime you detect a change to plot_factory.pkl
, or dashboard.yaml
:
$ watchmedo shell-command -p "./plot_factory.pkl;./dashboard.yaml" -c 'kill -HUP $(cat gunicorn.pid)'
Or in a single shell script:
start_server.sh:
trap "kill 0" EXIT # ensures that all four process are killed upon exit
source venv/bin/activate # activate virtual environment first
gunicorn --pid gunicorn.pid dashboard:app &
watchmedo shell-command -p "./covid.csv;./plot_factory.yaml" -c 'python build_plot_factory.py' &
watchmedo shell-command -p "./plot_factory.pkl;./plot_factory.yaml" -c 'python build_dashboard.py' &
watchmedo shell-command -p "./plot_factory.pkl;./dashboard.yaml" -c 'kill -HUP $(cat gunicorn.pid)'
wait # wait till user hits ctrl-c to exit and kill all three processes