Deploying a Dash app with gunicorn
with Dash dcc.Interval
† as well as dcc.Location
⁂ (i.e., multi-page app) features present*
* 参考 Dash 文档以了解上述组件:
† Interval https://dash.plotly.com/dash-core-components/intervaldcc(dash 核心组件)组件允许在指定的时间自动更新源数据(从而自动更新页面上的某些组件)间隔.
⁂ Location https://dash.plotly.com/dash-core-components/locationdcc 组件允许访问当前的 href/路径名/URL,从而启用多页面 Dash 应用程序。
[最小] 代码设置
所以我根据你提供的内容来完成这个工作——尽管你肯定还有更多的工作要做,以完成我离开你的地方(希望这是有道理的;有多种方法可以做到这一点)。顺便说一句,我设置max_intervals=5
为了dcc.Interval
组件,因为否则它只会无休止地更新图表(也许这就是您想要的行为,但是,设置此限制只会有助于简化此演示)。
这是我得到的文件结构,后面是每个文件的确切内容。
.
|-- app
| |-- __init__.py
| |-- gunicorn
| | `-- logs
| | `-- 20211101
| | `-- 20211101.access.log
| `-- interval.py
|-- index.py
`-- launch_gunicorn.sh
4 directories, 5 files
坚持您最关注的方法,即本质上将布局、回调和应用程序全部声明在一个文件中 - ”interval.py
“。然后,就只剩下一个额外的Python文件了”index.py
“(再次与您的帖子保持一致)它控制应用程序的多页面布局处理,也是其中的文件gunicorn
将获得对 Dash 应用程序对象的所需引用(特别是应用程序的服务器子属性;即,在本例中,为app.interval.app.server
)。最后,我创建了另外三分之一的额外文件,它只是一个 bashlaunch_gunicorn.sh
shell 脚本有助于执行应用程序的非开发/调试(即生产[虽然可能也想添加 nginx,但这是另一个问题])部署。
1) app/interval.py
import dash
import numpy as np
import pandas as pd
import plotly.express as px
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
interval_layout = html.Div(
className="row",
children=[
html.Div(
children=[
html.Div(id="interval-update"),
dcc.Graph(id="a-graph", style={"display": "flex"}),
dcc.Graph(id="b-graph", style={"display": "flex"}),
dcc.Interval(
id="interval-component", interval=1000, max_intervals=5
),
]
)
],
)
@app.callback(
[
Output("a-graph", "figure"),
Output("b-graph", "figure"),
Output("interval-update", "children"),
],
[Input("interval-component", "n_intervals")],
)
def update_graphs(n):
app.logger.info(f"callback happened: {n}")
df = pd.DataFrame(
{
"time": pd.Series(
pd.date_range("1-nov-2021", "2-nov-2021", freq="S")
).sample(30),
"bacteria_count": np.random.randint(0, 500, 30),
"bacteria_type": np.random.choice(list("AB"), 30),
}
)
df["epoch_time_ms"] = df["time"].astype(int) / 1000
df = df.sort_values("time")
a_fig = px.line(
df,
x="time",
y="bacteria_count",
line_shape="hv",
markers=True,
color="bacteria_type",
)
a_fig.update_traces(mode="markers+lines", hovertemplate=None)
a_fig.update_layout(hovermode="x unified")
b_fig = px.line(
df,
x="epoch_time_ms",
y="bacteria_count",
line_shape="hv",
markers=True,
color="bacteria_type",
)
b_fig.update_traces(mode="markers+lines", hovertemplate=None)
b_fig.update_layout(hovermode="x unified")
interval_update = [html.H1(f"Interval Update - {n}")]
return a_fig, b_fig, interval_update
if __name__ == "__main__":
app.run_server(debug=True)
2) index.py
""" ## Primary Application Deployment Script
------------------------------------
> ????????????????????????????????????????
_____
ᵂʳⁱᵗᵗᵉⁿ ᵇʸ
[your name]
Purpose / Overview:
------------------
This script serves as the de facto WSGI Production-level deployment module (in this case, fed to Gunicorn).
For Development-level deployment (DEBUG),
simply run this script, like so:
$ python index.py
: : : ::::::: : : :
"""
import logging
from dash import dcc
from dash import html
from dash import no_update
from dash.dependencies import Input
from dash.dependencies import Output
from dash.dependencies import State
from dash.exceptions import PreventUpdate
from app import interval
from app.interval import app
app.layout = html.Div(
[
# represents the URL bar, doesn't render anything
dcc.Location(id="url", refresh=False),
dcc.Link("Top | ", href="/"),
dcc.Link("Interval | ", href="/apps/interval"),
# content will be rendered in this element
html.Div(id="page-content"),
]
)
@app.callback(Output("page-content", "children"), Input("url", "pathname"))
def display_page(pathname):
if pathname == "/apps/interval":
return interval.interval_layout
elif pathname == "/":
return no_update
else:
return html.Div(
[html.H1("404 Page Not Found")],
style={"margin": "10%", "textAlign": "center"},
)
if __name__ == "__main__":
app.run_server(debug=True)
############################################
# # DEPLOY* MODE: PRODUCTION # #
# # [*WSGI import (e.g. Gunicorn+Nginx)] # #
############################################
if __name__ != "__main__":
logger = logging.getLogger(__name__)
logging.getLogger("matplotlib.font_manager").disabled = True
logging.basicConfig(
format="%(asctime)s %(name)-12s %(module)s.%(funcName)s %(processName)s %(levelname)-8s %(relativeCreated)d %(message)s",
level=logging.INFO,
)
gunicorn_logger = logging.getLogger("gunicorn.error")
app.logger.handlers = logger.handlers
app.logger.setLevel(logger.level)
app.logger.info(
"Initializing dash-webapp-template (App) `app.server` for handoff..."
)
server = app.server
3) launch_gunicorn.sh
#!/bin/bash
# Gunicorn Launch script for Flask-based Dash app,
# ran concurrently, & in parallel.
TODAY=$(date "+%Y%m%d")
TIMESTAMP=$(date | tr -d "[[:punct:]]" | tr -d ' ')
PORT=${1:-9001}
NUM_WORKERS=${2:-4}
THREADS=${3:-8}
GUNICORN_PROD_LOGS="./app/gunicorn/logs/${TODAY}"
mkdir -p $GUNICORN_PROD_LOGS
gunicorn \
-b 0.0.0.0:$PORT \
-w $NUM_WORKERS \
--worker-class gthread \
--threads $THREADS \
--name "dash-webapp-demo${TODAY}" \
--max-requests 100 \
--max-requests-jitter 10 \
--access-logfile "${GUNICORN_PROD_LOGS}/${TODAY}.access.log" \
index:server
通过部署应用程序Gunicorn https://docs.gunicorn.org/en/stable/run.html#gunicorn
启动后launch_gunicorn.sh
,您应该会看到终端中打印出以下输出:
[...]$ ./launch_gunicorn.sh
[2021-11-01 22:51:28 -0700] [23576] [INFO] Starting gunicorn 20.1.0
[2021-11-01 22:51:28 -0700] [23576] [INFO] Listening at: http://0.0.0.0:9001 (23576)
[2021-11-01 22:51:28 -0700] [23576] [INFO] Using worker: gthread
[2021-11-01 22:51:28 -0700] [23580] [INFO] Booting worker with pid: 23580
[2021-11-01 22:51:28 -0700] [23581] [INFO] Booting worker with pid: 23581
[2021-11-01 22:51:28 -0700] [23582] [INFO] Booting worker with pid: 23582
[2021-11-01 22:51:28 -0700] [23583] [INFO] Booting worker with pid: 23583
2021-11-01 22:51:30,464 app.interval index.<module> MainProcess INFO 2686 Initializing dash-webapp-template (App) `app.server` for handoff...
2021-11-01 22:51:30,489 app.interval index.<module> MainProcess INFO 2712 Initializing dash-webapp-template (App) `app.server` for handoff...
2021-11-01 22:51:30,560 app.interval index.<module> MainProcess INFO 2783 Initializing dash-webapp-template (App) `app.server` for handoff...
2021-11-01 22:51:30,621 app.interval index.<module> MainProcess INFO 2844 Initializing dash-webapp-template (App) `app.server` for handoff...
这是因为我将默认工作人员数量设置为 4。您可以在launch_gunicorn.sh
文件,或者作为参数传递。如您所见,该脚本接受三个optional参数:分别是端口、工作线程数和线程数。
然后我们会看到以下非常基本的主应用程序页面:
单击“间隔”链接会显示:
这是通过 Dash 进行的第五次也是最后一次自动“间隔”页面更新dcc.Interval
主要组件interval.py
应用程序布局+回调(您采用了一种一体化方法 - 效果很好!)文件。
在主动部署期间监控应用程序
我们可以在终端中看到记录的以下更新:
2021-11-01 23:11:26,140 app.interval interval.update_graphs MainProcess INFO 175805 callback happened: None
2021-11-01 23:11:26,190 numexpr.utils utils._init_num_threads MainProcess INFO 175854 NumExpr defaulting to 4 threads.
2021-11-01 23:11:27,270 app.interval interval.update_graphs MainProcess INFO 176935 callback happened: 1
2021-11-01 23:11:28,131 app.interval interval.update_graphs MainProcess INFO 177796 callback happened: 2
2021-11-01 23:11:29,129 app.interval interval.update_graphs MainProcess INFO 178793 callback happened: 3
2021-11-01 23:11:30,136 app.interval interval.update_graphs MainProcess INFO 179801 callback happened: 4
2021-11-01 23:11:31,133 app.interval interval.update_graphs MainProcess INFO 180797 callback happened: 5
并且在app/gunicorn/logs/[today's date]/[today's date].access.log
自动创建的文件,您将找到以下信息:
127.0.0.1 - - [01/Nov/2021:23:08:43 -0700] "GET / HTTP/1.1" 200 1781 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:43 -0700] "GET /_dash-layout HTTP/1.1" 200 462 "http://0.0.0.0:9001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:43 -0700] "GET /_dash-dependencies HTTP/1.1" 200 357 "http://0.0.0.0:9001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:44 -0700] "POST /_dash-update-component HTTP/1.1" 204 0 "http://0.0.0.0:9001/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:53 -0700] "GET /_dash-component-suites/dash/dcc/dash_core_components-shared.js.map HTTP/1.1" 200 26038 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:53 -0700] "GET /_dash-component-suites/dash/html/dash_html_components.min.js.map HTTP/1.1" 200 687871 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:53 -0700] "GET /_dash-component-suites/dash/dash_table/bundle.js.map HTTP/1.1" 200 154018 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:08:53 -0700] "GET /_dash-component-suites/dash/dcc/dash_core_components.js.map HTTP/1.1" 200 1996412 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:26 -0700] "POST /_dash-update-component HTTP/1.1" 200 651 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:26 -0700] "GET /_dash-component-suites/dash/dcc/async-graph.js HTTP/1.1" 304 0 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:26 -0700] "GET /_dash-component-suites/dash/dcc/async-graph.js.map HTTP/1.1" 200 75008 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:26 -0700] "GET /_dash-component-suites/dash/dcc/async-plotlyjs.js HTTP/1.1" 200 3594037 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:27 -0700] "POST /_dash-update-component HTTP/1.1" 200 16924 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:27 -0700] "POST /_dash-update-component HTTP/1.1" 200 16915 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:28 -0700] "POST /_dash-update-component HTTP/1.1" 200 16909 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:29 -0700] "POST /_dash-update-component HTTP/1.1" 200 16911 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:30 -0700] "POST /_dash-update-component HTTP/1.1" 200 16903 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
127.0.0.1 - - [01/Nov/2021:23:11:31 -0700] "POST /_dash-update-component HTTP/1.1" 200 16915 "http://0.0.0.0:9001/apps/interval" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
显示服务器收到的请求。
最后,运行例如htop
并向下过滤(在这种情况下,我只是过滤“破折号”,因为在launch_gunicorn.sh
在gunicorn命令选项文件中,有一个配置可以为你的gunicorn工作进程指定一个特定的名称;见下文)对于我们的应用程序,我们可以看到实际的gunicorn工人:
如果有任何疑问/问题,请留言 ????