背景
Plotly Dash 是一个 Python web 利用框架,它能帮忙咱们疾速建设难看的,响应式的,可交互的,数据可视化页面。惋惜的是,一个只能展现数据的利用并不总是非常有用。如果咱们须要一个残缺的 web 利用,那就不得不想方法利用它的后端 Flask.
问题
只管 Dash 借了 Flask 的壳,但这个 Flask 运行在 sandbox 里,而且和一般的 Flask 相比也少了很多性能。比方以下的性能要不没有要不须要降级到 Dash enterprise 版本。
- 数据库集成
- 认证
- 多页面、多路由反对
- 自定义格调
等等
设计
为了克服以上的所有问题,与其利用 Dash 自带的 Flask,咱们齐全能够创立一个根底的 Flask 利用,而后将咱们的 Dash 利用置于其上。
准则
- 灵便 — 可能用 Dash 和 Flask 创立任意类型的利用。
- 全功能 — Dash 和 Flask 都必须是全功能的,不能有性能上的限度。
- 可定制 — 可能自定义利用的款式。
- 简洁 — 可能简略快捷的创立一个 Dash 利用。
解决方案
总的来说,咱们能够有两种形式实现目标。
- __子利用__: 创立一个根底的 Flask 利用,用这个 Flask 作为 parent server 初始化 Dash 利用,将 Dash 利用用自定义路由注册为子利用。
- __iframe__: 创立一个根底的 Flask 利用,将 Dash 利用放在一个
iframe
中,再用 Flask 去加载这些iframe
.
哪个更好
和将 Dash 利用放在 iframe
中相比,只管这种形式看起来最简略,然而因为 iframe
齐全隔离的个性,反而会引入其余问题:
- __难于定制__:
iframe
不能通过 css/js 扭转iframe
内的利用。 - __不统一__:
iframe
因为和 main frame 有着不同的路由零碎,点击一个iframe
外部的链接并不会触发 main frame 的跳转。 - __无奈扩大__:
iframe
计划不反对多页面的 Dash 利用。
基于以上理由,咱们认为应用子利用是一个更灵便且更普适的计划。
代码构造
根底 Flask 的代码构造如下:
├── app
│ ├── dash_apps -- 所有的 Dash 利用都在这个目录
│ │ ├── custom_dash_app.py
│ │ ├── experiment_detail_dash_app.py
│ │ ├── experiment_list_dash_app.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── static
│ │ └── styles.css -- Custom CSS styles
│ ├── templates
│ │ ├── dash_layout.html -- Dash 利用的 layout
│ │ ├── header.html -- Header
│ │ ├── index.html -- 主页面
│ │ └── layout.html -- 主页面的 layout
│ └── views.py -- Flask 路由和主页面导航菜单定义
├── config.py
├── poetry.lock
├── poetry.toml
├── pyproject.toml
├── README.md
├── scripts
│ ├── fix.sh
│ ├── run.sh
│ └── setup.sh
├── setup.cfg
└── wsgi.py
残缺 demo 实现请参考 github。
实现细节
为了实现子利用,咱们须要实现如下几个要害性能。
- 创立根底利用的 blueprint
- 在 Flask 中注册根底利用的 blueprint
- 将 Dash 和根底利用关联起来,并定义路由和 layout
创立根底利用的 blueprint:
Blueprint 是 Flask 用来实现模块化利用的组件。
app/views.py
from flask import Blueprint, render_template
base_app = Blueprint("base_app", __name__)
@base_app.route("/")
def index():
"""Landing page."""
return render_template("index.html", top_menu_items=get_top_menu_items("/"))
在 Flask 中注册根底利用的 blueprint
在 Flask 中,这叫作 application factory 模式,创立一个 create_app
函数,返回 application 对象。Flask 会调用这个函数来解决申请。
app/__init__.py
from flask import Flask
from app.views import base_app
def create_app(test_config=None):
"""Create and configure Flask app"""
app = Flask(__name__)
app.register_blueprint(base_app)
return app
将 Dash 和根底利用关联起来,并定义路由和 layout
创立一个应用根底利用的 Dash 利用。
app/dash_apps/__init__.py
def customize_index_string(app, url):
"""Custom app's index string"""app.index_string = env.get_template("dash_layout.html").render(top_menu_items=get_top_menu_items(url)
)
def add_route(app, url):
"""Add route to the app"""
app.server.add_url_rule(url, endpoint=url, view_func=app.index)
app.routes.append(url)
def create_dash_app(server, url_rule, url_base_pathname):
"""Create a Dash app with customized index layout
:param server: base Flask app
:param url_rule: url rule as endpoint in base Flask app
:param url_base_pathname: url base pathname used as dash internal route prefix
"""
app = dash.Dash(name=__name__, server=server, url_base_pathname=url_base_pathname)
customize_index_string(app, url_rule)
add_route(app, url_rule)
return app
app/dash_apps/custom_dash_app.py
from app.dash_apps import create_dash_app
# endpoint of this page
URL_RULE = "/custom-app"
# dash internal route prefix, must be start and end with "/"
URL_BASE_PATHNAME = "/dash/custom-app/"
def create_dash(server):
"""Create a Dash view"""
app = create_dash_app(server, URL_RULE, URL_BASE_PATHNAME)
# dash app definitions goes here
...
return app.server
如何在根底利用里增加更多 Dash 利用
在根底利用里增加 Dash 总共须要 2 步。
第一步:创立 Dash 利用
1-1: 在 app/dash_apps
目录创立一个 .py
文件。
1-2: 依照以下代码构造创立 Dash 利用。
from app.dash_apps import create_dash_app
# endpoint of this page
URL_RULE = "/custom-app"
# dash internal route prefix, must be start and end with "/"
URL_BASE_PATHNAME = "/dash/custom-app/"
def create_dash(server):
"""Create a Dash view"""
app = create_dash_app(server, URL_RULE, URL_BASE_PATHNAME)
# dash app definitions goes here, same as what you would do in normal Dash application
...
return app.server
第二步: 在 app/views.py
为 Dash 利用增加主页面导航菜单(可选)
top_menus = [{"path": "/", "title": "Home"},
{"path": "/experiments", "title": "Experiments"},
{"path": "/custom-app", "title": "Custom App"},
...
]
如何运行
执行以下脚本启动利用,如果设置了FLASK_ENV=development
,利用会以开发模式运行。
#!/bin/bash
source .venv/bin/activate
if [["${FLASK_ENV}" == "development" ]]; then
flask run --host=0.0.0.0 --port 8050
else
gunicorn wsgi:app \
--bind 0.0.0.0:8050 \
--log-level debug \
--workers 2 \
--threads 4
fi