Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Dash in Flask #263

Merged
merged 5 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions examples/flask/dash-in-flask/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Integrating Dash apps into a Flask app

Interactive Flask application, with Dash applications integrated using:
- App context: By passing the Flask instance directly into each Dash app and incorporating them into the context of our Flask app, we allow the Dash apps to share data and other resources belonging to the Flask app. However, since each of these app is a component of the main Flask app, adding too many Dash app components may overload the Flask server. See `app_ctx.py` for implementation. For more information, see the [documentation](https://flask.palletsprojects.com/en/2.3.x/appcontext/).
- Middleware: Using middleware decouples each Dash app from the Flask app and each other, meaning they will be considered independent apps. This allows better horizontal scaling if we need to add a large amount of Dash apps, but resource sharing between apps may take extra steps compared to using app context. See `app_middleware.py` for implementation. For more information, see the [documentation](https://flask.palletsprojects.com/en/2.3.x/patterns/appdispatch/).


|![Main page](app.png)|
|:--:|
| *Main page* |

|![](app_dash1.png)|
|:--:|
| *Dash App 1 (Simple App)* |

|![](app_dash2.png)|
|:--:|
|*Dash App 2 (Population App)*|

## Local testing

Run `gunicorn app_ctx:app run --bind 0.0.0.0:80`. Replace `app_ctx` with `app_middleware` if you want to run the middleware version. You should be able to access the app at `0.0.0.0:80`.

## Upload to Ploomber Cloud

To run either application on Ploomber, first copy their content to `app.py`, For example
```bash
cp app_ctx.py app.py
```

Next, zip `app.py` together with `utils.py`, `requirements.txt` and `dash_apps` folder, and upload to Ploomber Cloud. For more details, please refer to our [Flask deployment guide](https://docs.cloud.ploomber.io/en/latest/apps/flask.html).
bryannho marked this conversation as resolved.
Show resolved Hide resolved

## Adding your own Dash app

To test using your own Dash app, ensure that your dash app (e.g. `dash.py`) is in the `dash_app` folder. Make sure to format `dash.py` as below:
```python
from dash import Dash, html, dcc, callback, Output, Input
import plotly.express as px
import pandas as pd
from flask import g # If you want to use Flask context global value

def init_app(url_path, server=None):
# Initialize Dash app with or without specifying flask server
app = Dash(server=server, url_base_pathname=url_path) if server else Dash(requests_pathname_prefix=url_path)

app.title = "Your Dash App"

app.layout = html.Div("Write your Dash app layout here.")

init_callbacks(app)
return app.server


def your_update_func(input):
pass

def init_callbacks(app):
app.callback(
Output("your-output-id", "output"),
Input("your-input-id", "input")
)(your_update_func)
```

Next, in `utils.py`, import your app and add it to `DASH_APPS_`.
```python
from dash_apps import dash

DASH_APPS_ = {
...,
'/your_app_url': (dash.init_app, "Your app name")
}
```

Re-run the Flask application, and you will see your app's URL added to the HTML display.
Binary file added examples/flask/dash-in-flask/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions examples/flask/dash-in-flask/app_ctx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from flask import Flask, render_template_string, g
from utils import construct_dash_lists
import ssl
import pandas as pd

ssl._create_default_https_context = ssl._create_unverified_context

app = Flask(__name__)

with app.app_context():
# Define Flask context variables to be used in apps
g.df = pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv"
)

# Obtain URL mapping of Dash apps and list items in template
dash_url_mapping, list_items = construct_dash_lists(app)

# Add Dash app to Flask context
for dash_app in dash_url_mapping.values():
app = dash_app

@app.route("/")
def hello_world():
return render_template_string(f"""
<h1>Main Flask App</h1>
<h2>Select your Dash App</h2>
<ul>
{list_items}
</ul>""")
Binary file added examples/flask/dash-in-flask/app_dash1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/flask/dash-in-flask/app_dash2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions examples/flask/dash-in-flask/app_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from flask import Flask, render_template_string
from utils import construct_dash_lists
from werkzeug.middleware.dispatcher import DispatcherMiddleware

# Initialize Flask server
flask_app = Flask(__name__)

# List of Dash apps to add as middleware
dash_mw = {}

# Obtain URL mapping of Dash apps and list items in template
dash_url_mapping, list_items = construct_dash_lists()

# Integrate Flask and Dash apps using DispatcherMiddleware
app = DispatcherMiddleware(flask_app, dash_url_mapping)

@flask_app.route("/")
def index():
return render_template_string(f"""
<h1>Main Flask App</h1>
<h2>Select your Dash App</h2>
<ul>
{list_items}
</ul>""")
27 changes: 27 additions & 0 deletions examples/flask/dash-in-flask/dash_apps/dash1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from dash import dcc, html, Input, Output, Dash
from flask import request
def init_app(url_path, server=None):

# Initialize Dash app with or without specifying flask server
app = Dash(server=server, url_base_pathname=url_path) if server else Dash(requests_pathname_prefix=url_path)

app.layout = html.Div(children=[
html.A(id="logout-link", children="Main page", href="/"),
html.H1("Welcome to my Dash App", style={"textAlign": "center"}),
html.Div(id="dummy"),
dcc.Graph(
id='example-graph',
figure={
'data': [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'Category 1'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': 'Category 2'},
],
'layout': {
'title': 'Dash Data Visualization'
}
}
),

])

return app.server
47 changes: 47 additions & 0 deletions examples/flask/dash-in-flask/dash_apps/dash2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Taken from Dash's documentation: https://dash.plotly.com/minimal-app
"""
from dash import Dash, html, dcc, callback, Output, Input
import plotly.express as px
import pandas as pd
import ssl
from flask import g

ssl._create_default_https_context = ssl._create_unverified_context

def init_app(url_path, server=None):
global df

# Initialize Dash app with or without specifying flask server
app = Dash(server=server, url_base_pathname=url_path) if server else Dash(requests_pathname_prefix=url_path)

# Use data from server context if server is available
df = g.df if server else pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv"
)
server = app.server

app.title = "Population by country - Ploomber Cloud Dash Application"

app.layout = html.Div(
[
html.A(id="logout-link", children="Main page", href="/"),
html.H1(children="Population by country", style={"textAlign": "center"}),
dcc.Dropdown(df.country.unique(), "Canada", id="dropdown-selection"),
dcc.Graph(id="graph-content"),
]
)

init_callbacks(app)
return server


def update_graph(value):
dff = df[df.country == value]
return px.line(dff, x="year", y="pop")

def init_callbacks(app):
app.callback(
Output("graph-content", "figure"),
Input("dropdown-selection", "value")
)(update_graph)
4 changes: 4 additions & 0 deletions examples/flask/dash-in-flask/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask
dash
pandas
gunicorn
24 changes: 24 additions & 0 deletions examples/flask/dash-in-flask/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dash_apps import dash1, dash2

# Setup Dash apps.
# For key, use your preferred URL for app.
# For value, add initialization function and app name for each app
DASH_APPS_ = {
'/app1': (dash1.init_app, "Simple App"),
'/app2': (dash2.init_app, "Population App")
}

def construct_dash_lists(app=None):
"""Return a dictionary of URLs mapped to Dash apps, and a string HTML list items,
each being a link to the Dash app corresponding to its name."""
# List of Dash apps to add as middleware
dash_url_mapping = {}

# List items in the template: URLs to each dash app
list_items = ""

for url in DASH_APPS_:
dash_url_mapping[url] = DASH_APPS_[url][0](url + "/", app)
list_items += "<li><a href=\"" + url + "/\">" + DASH_APPS_[url][1] + "</a></li>\n"

return dash_url_mapping, list_items