如何在Flask中整合Dash應用

BetaRabbit發表於2022-03-12

背景

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

相關文章