Black Lives Matter. Please consider donating to Black Girls Code today.

Dynamic replacement of Dash Apps, bad idea or very bad idea?

I’m building an application that users can configure their own layout, they can select graphs they want, and they may select multiple of the same graphs and configure them differently.

This means that many callback targets can’t be figured out till run-time as they depend on the users layout choices. I have come up with a solution for this, dynamically create new dash apps from a callback.

Here is a dirty proof of concept:

import dash
from flask import Flask
import dash_core_components as dcc
import dash_html_components as html
from dash.exceptions import PreventUpdate


# Set-up Flask
server = Flask(__name__)

@server.route('/')
def index():
    return "Root Path"

# Set-up Multiple Dash
root_dash = dash.Dash(server=server, url_base_pathname='/notused/')
root_dash.layout = html.Div()

app1 = dash.Dash(server=server, url_base_pathname='/dash1/')
app1.layout = html.Div([
    html.Div('Target 1')
    ])

app2 = dash.Dash(server=server, url_base_pathname='/dash2/')
app2.layout = html.Div([
    html.Div('Target 2'),
    html.Div(id='target'),
    html.Button(id='submit', n_clicks=0, children='Update dash1 server')
    ])

# This dict is used for tracking what dash apps I have created
ALL_DASH_APPS = {'app1': app1, 'app2': app2}
del app1  # In this case we don't need to keep app1 in global namespace


@app2.callback(output=dash.dependencies.Output('target', 'children'),
               inputs=[dash.dependencies.Input('submit', 'n_clicks')])
def crazy_dash_creation_callback(n_clicks):
    if not n_clicks:
        raise PreventUpdate

    # This is the dicey part, remove the endpoints from the Flask Server
    while True:
        for i in range(len(server.url_map._rules)):
            if server.url_map._rules[i].endpoint.startswith('/dash1/'):
                del server.url_map._rules[i]
                break
        else:
            break

    # And remove the view functions
    for key in list(server.view_functions.keys()):
        if key.startswith('/dash1/'):
            del server.view_functions[key]

    # Create New Dash App and Callbacks, using same url_bash_pathname
    new_dash_app = dash.Dash(server=server, url_base_pathname='/dash1/')
    new_dash_app.layout = html.Div([
        html.Div('New Target with new functionality:'),
        dcc.Textarea(id='input'),
        html.Div(id='output')
        ])

    @new_dash_app.callback(output=dash.dependencies.Output('output', 'children'),
                           inputs=[dash.dependencies.Input('input', 'value')])
    def mirror_text(text_input):
        if text_input is None:
            raise PreventUpdate
        return text_input


    # Replace Dash App we are keeping track of
    ALL_DASH_APPS['app1'] = new_dash_app


    return "Updated Dash1"


if __name__ == '__main__':
    root_dash.run_server(port=8050, host='0.0.0.0')

In this code if you go to “mywebsite.domain/dash1” you get served a page that says “Target 1”, then if you go to “mywebsite.domain/dash2” and click the button you get a response “Updated Dash1”. Finally if you go back to “mywebsite.domain/dash1” you get a whole new web page that wasn’t the original dash1.

Obviously this is dicey and I am in a situation where I can’t complain if this breaks in future versions of Flask or Dash, but is there currently any other realistic alternative?