Search code examples
pythondataframeplotlyglobal-variablesplotly-dash

Run multiple Dash apps with serve layout functions


I am currently trying to run multiple Plotly Dash apps with serve layout functions (see code bellow with serve_layout1() and serve_layout2() functions). I need them because I have to include several global variables in layout so my app will be updated when someone click refresh button in a browser. Multiple Dash apps work fine without serve layout functions. However, when I try to run multiple apps with serve layout functions, things start going awry.

Here is my file structure:

- app.py
- index.py
- apps
   |-- __init__.py
   |-- app1.py
   |-- app2.py

Where app.py:

import dash
app = dash.Dash(__name__, suppress_callback_exceptions=True)
server = app.server

index.py:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

from app import app
from apps import app1, app2

from apps.app1 import serve_layout1
from apps.app2 import serve_layout2

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
              Input('url', 'pathname'))
def display_page(pathname):
    if pathname == '/apps/app1':
        app1.layout = serve_layout1
        return app1.layout
    elif pathname == '/apps/app2':
        app2.layout = serve_layout2
        return app2.layout
    else:
        return '404'

if __name__ == '__main__':
    app.run_server(debug=True)

app1.py:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from app import app

def serve_layout1():

    global df

    # uploading cvs as dataframe, doing some computation here and etc (skipped)

    app_layout1 = html.Div([
        html.H3('App 1'),
        dcc.Dropdown(
            id='app-1-dropdown',
            options=[
                {'label': 'App 1 - {}'.format(i), 'value': i} for i in [
                    'NYC', 'MTL', 'LA'
                ]
            ]
        ),
        html.Div(id='app-1-display-value'),
        dcc.Link('Go to App 2', href='/apps/app2')
    ])

    return app_layout1

@app.callback(
    Output('app-1-display-value', 'children'),
    Input('app-1-dropdown', 'value'))
def display_value(value):
    return 'You have selected "{}"'.format(value)

app2.py:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from app import app

def serve_layout2():

    global df

    # uploading cvs as dataframe, doing some computation here and etc (skipped)

    app_layout2 = html.Div([
        html.H3('App 2'),
        dcc.Dropdown(
            id='app-2-dropdown',
            options=[
                {'label': 'App 2 - {}'.format(i), 'value': i} for i in [
                    'NYC', 'MTL', 'LA'
                ]
            ]
        ),
        html.Div(id='app-2-display-value'),
        dcc.Link('Go to App 2', href='/apps/app1')
    ])


    return app_layout2


@app.callback(
    Output('app-1-display-value', 'children'),
    Input('app-1-dropdown', 'value'))
def display_value(value):
    return 'You have selected "{}"'.format(value)

When I run the app I am getting the following error:

Traceback (most recent call last):
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\dash\dash.py", line 1039, in add_context
    response, cls=plotly.utils.PlotlyJSONEncoder
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\json\__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\_plotly_utils\utils.py", line 45, in encode
    encoded_o = super(PlotlyJSONEncoder, self).encode(o)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\_plotly_utils\utils.py", line 115, in default
    return _json.JSONEncoder.default(self, obj)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\json\encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'function' is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\dash\dash.py", line 1072, in dispatch
    response.set_data(func(*args, outputs_list=outputs_list))
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\dash\dash.py", line 1042, in add_context
    _validate.fail_callback_output(output_value, output)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\dash\_validate.py", line 256, in fail_callback_output
    _validate_value(val, index=i)
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\dash\_validate.py", line 251, in _validate_value
    toplevel=True,
  File "C:\Users\iakubal\Anaconda3\envs\general_3_6\lib\site-packages\dash\_validate.py", line 205, in _raise_invalid
    bad_val=bad_val,
dash.exceptions.InvalidCallbackReturnValue: The callback for `<Output `page-content.children`>`
returned a value having type `function`
which is not JSON serializable.


The value in question is either the only value returned,
or is in the top level of the returned list,

and has string representation
`<function serve_layout1 at 0x0000016B49DABB70>`

In general, Dash properties can only be
dash components, strings, dictionaries, numbers, None,
or lists of those.

Solution

  • The problem is that you are not calling your serve_layout functions here:

    @app.callback(Output("page-content", "children"), Input("url", "pathname"))
    def display_page(pathname):
        if pathname == "/apps/app1":
            app1.layout = serve_layout1
            return app1.layout
        elif pathname == "/apps/app2":
            app2.layout = serve_layout2
            return app2.layout
        else:
            return "404"
    

    Instead you should do this:

    @app.callback(Output("page-content", "children"), Input("url", "pathname"))
    def display_page(pathname):
        if pathname == "/apps/app1":
            app1.layout = serve_layout1()
            return app1.layout
        elif pathname == "/apps/app2":
            app2.layout = serve_layout2()
            return app2.layout
        else:
            return "404"
    

    So you're returning a reference to a function instead of the dash components the function returns.

    This is not valid as the error message you've shared is saying:

    In general, Dash properties can only be dash components, strings, dictionaries, numbers, None, or lists of those.

    Also in app2.py you're referencing the wrong ids in the callback. You're referencing the ids for app1.py. You can't have duplicate callback outputs. So the callback in app2.py should look like this:

    @app.callback(
        Output("app-2-display-value", "children"), Input("app-2-dropdown", "value")
    )
    def display_value(value):
        return 'You have selected "{}"'.format(value)