Internal Server Error Dash app serving a Flask Server: A name collision occurred between blueprints

Viewed 557

I'm working with a Dash App inside a Flask Server.

This is how I instantiate the Flask Server:

from flask import Flask, redirect
server = Flask(__name__, template_folder="../frontend/templates",
               instance_relative_config=True)

This is the create_dash_app function:

import dash
def create_dash_app(server):

    dash_app = dash.Dash(
    __name__,
    server=server,
    url_base_pathname='/dash/',
    external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css']
    )

    dash_app.config['suppress_callback_exceptions'] = True

    dash_app.layout = html.H3("Lorem ipsum... ")

    return dash_app.server

At one of my server routes I have the following:

@server.route('/filter')
def filter_stuff():

    <some code>

    create_dash_app()
    redirect("/dash")

The point is that for my use case a user may want to see the dash app, go back to the filter route, apply some filters and check the changings in the dash app. Unfortunately, when I do that I get the following:

> AssertionError: A name collision occurred between blueprints
> <flask.blueprints.Blueprint object at 0x11905bcd0> and
> <flask.blueprints.Blueprint object at 0x118acbad0>. Both share the
> same name "_dash_dash_assets". Blueprints that are created on the fly
> need unique names.

Given my little experience with Flask and Dash I guess it's something trivial that I'm missing. Please tell me if there is additional info you may need.

EDIT: I've found a workaround which I'm ashamed of 2nd EDIT: included a callback example

from flask import Flask, redirect
from multiprocessing import Value
counter = Value('i', 0)

server = Flask(__name__, template_folder="../frontend/templates",
               instance_relative_config=True)


@server.route('/filter')
def filter_stuff():
    with counter.get_lock():
        counter.value += 1
        out = counter.value
    <some code>

    dash_address = "/dash"+str(out)+"/"
    create_dash_app(server, dash_address, nodes_cyto, edges_cyto)

    redirect(dash_address)


import dash
from dash_core_components import *
from dash.dependencies import Input, Output
def create_dash_app(server, dash_address, nodes_cyto, edges_cyto):

    dash_app = dash.Dash(
    __name__,
    server=server,
    url_base_pathname=dash_address,
    external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css']
    )

    dash_app.config['suppress_callback_exceptions'] = True

    dash_app.layout = html.Div([
            cyto.Cytoscape(
                id='cytoscape_net',
                elements=nodes_cyto + edges_cyto,
                zoomingEnabled=True,
                zoom=0.8,
                layout={'name': 'klay'},
                style={'width': '80%', 'height': '700px', 'float': 'left'},
                stylesheet=my_stylesheet 
            ),
        dcc.Tabs([
            dcc.Tab(label='Dettaglio Interazioni',  style=tab_style, selected_style=tab_selected_style, children=[
                dash_table.DataTable(
                    id='edge-table',
                    columns=[],
                    style_header={
                        'backgroundColor': 'rgb(230, 230, 230)',
                        'fontWeight': 'bold'
                    },
                    fixed_rows={'headers': True},
                    style_data = {'whiteSpace': 'normal',
                                  'height': 'auto',
                                    'lineHeight': '15px'},
                    style_data_conditional=[
                        {
                            'if': {'row_index': 'odd'},
                            'backgroundColor': 'rgb(248, 248, 248)'
                        },
                        {
                            'if': {'column_id': 'Info'},
                            'textAlign': 'right'
                        }
                    ],
                    style_cell={
                        'height': 'auto',
                        'minWidth': '180px', 'width': '180px', 'maxWidth': '180px',
                        'whiteSpace': 'normal',
                        'fontSize':14
                    },
                    style_table={'width': '50%',
                                 'overflowY': 'auto',
                                 'overflowX': 'auto'},
                    data=[])
            ])
    init_callbacks(dash_app)
    return dash_app.server

def init_callbacks(app):
    @app.callback([Output('edge-table', 'data'),
               Output('edge-table', 'columns')],
              [Input('cytoscape_net', 'tapEdgeData')])

    def populateEdgeTable(data):
        json_data = json.loads(json.dumps(data, indent=2))

        if data is None:
            return [], []
        dict_data = dict(json_data)

        dict_data_1 = {your_key.replace("_1", ""): dict_data[your_key] for your_key in
                   [j for j in dict_data.keys() if "_1" in j] + ["tipo_interazione"]}
        dict_data_2 = {your_key.replace("_2", ""): dict_data[your_key] for your_key in
                   [j for j in dict_data.keys() if "_2" in j] + ["tipo_interazione"]}

        columns = [{'name': 'Info', 'id': 'Info'},
               {'name': 'Source', 'id': 'Source'},
               {'name': 'Target', 'id': 'Target'}]

        return pd.DataFrame({'Info': list(dict_data_1.keys()),
                         'Source': list(dict_data_1.values()),
                         'Target': list(dict_data_2.values())}).to_dict(orient='records'), columns

As you can see I'm using a counter to redirect to a new page each time. I wonder if some people might die reading this.

Thank you

1 Answers

While Dash apps can be instantiated dynamically, in my experience this is a design pattern that should be avoided. Regarding the specific use case that is being targeted in this question, i would consider two main pathways,

Integrate the filters with the Dash app

The most simplistic and coherent solution would be to include the filters as part of the Dash app itself. With this approach, it would be possible to apply the filters to other page(s) by passing the filter values as a State argument to the page rendering callback(s). They could either be passed directly (if there are not too many filters) or aggregated via e.g. a Store component. Here is a code snippet to illustrate the concept,

@app.callback(Output("store", "data"), [Input("filter{}".format(i), "value") for i in range(100)])
def aggregate_filters(*args):
    return list(args)


@app.callback(Output("page", "children"), [Input(...)], [State("store", "data")])
def render_page(*args, filters):
    ...

Pass filter selections to the Dash app

If it is not possible to port the filter page itself to Dash, you could serialize the filter selections and pass them to the Dash app. It could be e.g. via the URL (if there are not too many selections) or a session variable. Here is a sketch of the latter,

from flask import session, redirect

...

@server.route('/filter')
def filter_stuff():
    # This assignment might happen from JavaScript depending on you app structure
    session["filters"] = ...  
    redirect(dash_address)

...

@app.callback(Output("page", "children"), [Input(...)])
def render_page(*args, filters):
    filters = session["filters"]
    ...

Other variations of this approach include saving the selections server side (e.g. in a file or a memory cache such a Redis) identified by a key, e.g. a uuid, which is then passed via the URL to the Dash app.

Regarding the hack the you are currently utilizing, in additional to being not-too-beautiful, i fear that it will deplete the server resources since you are effectively creating a new Dash app every time the filter selections change.

Related