pythonservergunicornplotly-dashmultipage

Dash multipage app server issue - dash.exceptions.NoLayoutException


Following guidelines to structure a multipage dash app here: https://dash.plotly.com/urls

Trying to replicate and adjust following the example in the link, adding one layer on top for execution.

My project directory is as such:

-- wsgi.py
|-- multipageapp
    |-- app.py
    |-- index.py
    |-- Procfile
    |-- apps
       |-- __init__.py
       |-- app1.py
       |-- app2.py

It works fine if I go in the multipageapp folder and execute the index.py file from there. However, I am getting the following error when executing the wsgi.py file, I am not sure what I am doing wrong, it seems like it match what's in the example:

Traceback (most recent call last):
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\app.py", line 1945, in full_dispatch_request
    self.try_trigger_before_first_request_functions()
  File "C:\Program Files\Anaconda3_4\lib\site-packages\flask\app.py", line 1993, in try_trigger_before_first_request_functions
    func()
  File "C:\Program Files\Anaconda3_4\lib\site-packages\dash\dash.py", line 1048, in _setup_server
    _validate.validate_layout(self.layout, self._layout_value())
  File "C:\Program Files\Anaconda3_4\lib\site-packages\dash\_validate.py", line 334, in validate_layout
    """

dash.exceptions.NoLayoutException: The layout was `None` at the time that 
`run_server` was called.
Make sure to set the `layout` attribute of your application
before running the server.

This is the content of wsgi.py

from multipageapp.app import server

if __name__ == "__main__":
    server.run(debug=True)

This is the content of Procfile

web: gunicorn index:app.server

This is the content of app.py

import dash

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

This is the content of 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


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':
        return app1.layout
    elif pathname == '/apps/app2':
        return app2.layout
    else:
        return '404'

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

This is the content of app1.py

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

from app import app

layout = 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')
])


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

This is the content of app2.py

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

from app import app

layout = 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 1', href='/apps/app1')
])


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

Solution

  • Found out a way to make it work. I simply moved the content of 'index.py' into 'wsgi.py', deleted 'index.py' and I adjusted the imports to have folder 'multipageapp' inside