背景
Plotly Dash 是一个Python web应用框架,它能帮助我们快速建立好看的,响应式的,可交互的,数据可视化页面。可惜的是,一个只能展示数据的应用并不总是十分有用。如果我们需要一个完整的web应用,那就不得不想办法利用它的后端 Flask.
问题
尽管Dash借了Flask的壳,但这个Flask运行在sandbox里,而且和普通的Flask相比也少了很多功能。比如以下的功能要不没有要不需要升级到Dash enterprise版本。
- 数据库集成
- 认证
- 多页面、多路由支持
- 自定义风格
等等
原则
- 灵活 -- 能够用Dash和Flask创建任意类型的应用。
- 全功能 -- Dash和Flask都必须是全功能的,不能有功能上的限制。
- 可定制 -- 能够自定义应用的样式。
- 简洁 -- 能够简单快捷的创建一个Dash应用。
- __子应用__: 创建一个基础的Flask应用,用这个Flask作为parent server初始化Dash应用,将Dash应用用自定义路由注册为子应用。
- __iframe__: 创建一个基础的Flask应用,将Dash应用放在一个
iframe
中,再用Flask去加载这些iframe
.
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是Flask用来实现模块化应用的组件。
app/views.py
from flask import Blueprint, render_templatebase_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 Flaskfrom app.views import base_appdef create_app(test_config=None):
"""Create and configure Flask app"""app = Flask(__name__)app.register_blueprint(base_app)return app
将Dash和基础应用关联起来,并定义路由和layout
【如何在Flask中集成Dash应用】创建一个使用基础应用的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/bashsource .venv/bin/activateif [[ "${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