Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

Deploying Multi-Page app to Heroku - not deploying as set up

Hi All. I’m writing because I’ve been working with dash over the past couple of months to develop (and eventually deploy - hopefully) a multi-page dashboard to Heroku. I’ve followed through all tutorials structured my app as recommended in “structuring a multi-page app” tutorial (and I’ve also successfully deployed one-page apps to Heroku without an issue). Anyway, I’m writing because I’m trying to deploy to Heroku and I open the page, I get an internal server error and when I check the heroku logs I get the following error:

AttributeError: 'NoneType' object has no attribute 'traverse'

Please note the error logs are modified anywhere you might expect an website sounding url because plotly isn’t letting me post links because I recently joined and they think I might be a spammer.

(dash_apps) RTI-102626:app_base cinkpen$ heroku logs
2018-01-20T18:14:30.757858+00:00 app[web.1]: Failed to find application: 'app'
2018-01-20T18:14:30.759669+00:00 app[web.1]: [2018-01-20 18:14:30 +0000] [8] [INFO] Worker exiting (pid: 8)
2018-01-20T18:14:30.879541+00:00 app[web.1]: Traceback (most recent call last):
2018-01-20T18:14:30.879852+00:00 app[web.1]:     self.sleep()
2018-01-20T18:14:30.880053+00:00 app[web.1]:     ready = select.select([self.PIPE[0]], [], [], 1.0)
2018-01-20T18:14:30.879855+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 359, in sleep
2018-01-20T18:14:30.879554+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 209, in run
2018-01-20T18:14:30.880213+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 524, in reap_workers
2018-01-20T18:14:30.880199+00:00 app[web.1]:     self.reap_workers()
2018-01-20T18:14:30.880057+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 244, in handle_chld
2018-01-20T18:14:30.880429+00:00 app[web.1]:     raise HaltServer(reason, self.WORKER_BOOT_ERROR)
2018-01-20T18:14:30.880465+00:00 app[web.1]: gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>
2018-01-20T18:14:30.880466+00:00 app[web.1]: 
2018-01-20T18:14:30.880467+00:00 app[web.1]: During handling of the above exception, another exception occurred:
2018-01-20T18:14:30.880474+00:00 app[web.1]: Traceback (most recent call last):
2018-01-20T18:14:30.880468+00:00 app[web.1]: 
2018-01-20T18:14:30.880476+00:00 app[web.1]:   File "/app/.heroku/python/bin/gunicorn", line 11, in <module>
2018-01-20T18:14:30.880572+00:00 app[web.1]:     sys.exit(run())
2018-01-20T18:14:30.880573+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 74, in run
2018-01-20T18:14:30.880683+00:00 app[web.1]:     WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
2018-01-20T18:14:30.880685+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/base.py", line 203, in run
2018-01-20T18:14:30.880825+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/base.py", line 72, in run
2018-01-20T18:14:30.880928+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 231, in run
2018-01-20T18:14:30.881064+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 344, in halt
2018-01-20T18:14:30.880823+00:00 app[web.1]:     super(Application, self).run()
2018-01-20T18:14:30.880925+00:00 app[web.1]:     Arbiter(self).run()
2018-01-20T18:14:30.881061+00:00 app[web.1]:     self.halt(reason=inst.reason, exit_status=inst.exit_status)
2018-01-20T18:14:30.881240+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 393, in stop
2018-01-20T18:14:30.881419+00:00 app[web.1]:     time.sleep(0.1)
2018-01-20T18:14:30.881421+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 244, in handle_chld
2018-01-20T18:14:30.881235+00:00 app[web.1]:     self.stop()
2018-01-20T18:14:30.881559+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 527, in reap_workers
2018-01-20T18:14:30.881769+00:00 app[web.1]:     raise HaltServer(reason, self.APP_LOAD_ERROR)
2018-01-20T18:14:30.881556+00:00 app[web.1]:     self.reap_workers()
2018-01-20T18:14:30.881785+00:00 app[web.1]: gunicorn.errors.HaltServer: <HaltServer 'App failed to load.' 4>
2018-01-20T18:14:30.961512+00:00 heroku[web.1]: State changed from up to crashed
2018-01-20T18:14:30.954592+00:00 heroku[web.1]: Process exited with status 1
2018-01-20T18:32:45.000000+00:00 app[api]: Build started by user cinkpen_at_someplace
2018-01-20T18:33:16.189288+00:00 heroku[web.1]: State changed from crashed to starting
2018-01-20T18:33:15.925286+00:00 app[api]: Release v7 created by user cinkpen_at_someplace
2018-01-20T18:33:15.925286+00:00 app[api]: Deploy 12a3da55 by user cinkpen_at_someplace
2018-01-20T18:32:45.000000+00:00 app[api]: Build succeeded
2018-01-20T18:33:28.504702+00:00 heroku[web.1]: Starting process with command `gunicorn app:server`
2018-01-20T18:33:30.600967+00:00 app[web.1]: [2018-01-20 18:33:30 +0000] [4] [INFO] Starting gunicorn 19.7.1
2018-01-20T18:33:30.601453+00:00 app[web.1]: [2018-01-20 18:33:30 +0000] [4] [INFO] Listening at: hteeteeeepeecolon-slash-slash0.0.0.0:28512 (4)
2018-01-20T18:33:30.601551+00:00 app[web.1]: [2018-01-20 18:33:30 +0000] [4] [INFO] Using worker: sync
2018-01-20T18:33:30.605174+00:00 app[web.1]: [2018-01-20 18:33:30 +0000] [8] [INFO] Booting worker with pid: 8
2018-01-20T18:33:30.615251+00:00 app[web.1]: [2018-01-20 18:33:30 +0000] [9] [INFO] Booting worker with pid: 9
2018-01-20T18:33:31.269528+00:00 heroku[web.1]: State changed from starting to up
2018-01-20T18:33:37.261611+00:00 heroku[router]: at=info method=GET path="/" host=fakenamequickdashdotsomething request_id=c7df2261-3b6b-470a-8eb4-8225920bf2fb fwd="161.69.123.11" dyno=web.1 connect=0ms service=6ms status=500 bytes=456 protocol=hteeteeeepees
2018-01-20T18:33:37.260685+00:00 app[web.1]: [2018-01-20 18:33:37,259] ERROR in app: Exception on / [GET]
2018-01-20T18:33:37.260710+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
2018-01-20T18:33:37.260705+00:00 app[web.1]: Traceback (most recent call last):
2018-01-20T18:33:37.260711+00:00 app[web.1]:     response = self.full_dispatch_request()
2018-01-20T18:33:37.260713+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/flask/app.py", line 1607, in full_dispatch_request
2018-01-20T18:33:37.260714+00:00 app[web.1]:     self.try_trigger_before_first_request_functions()
2018-01-20T18:33:37.260716+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/flask/app.py", line 1654, in try_trigger_before_first_request_functions
2018-01-20T18:33:37.260718+00:00 app[web.1]:     func()
2018-01-20T18:33:37.260719+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/dash.py", line 554, in _setup_server
2018-01-20T18:33:37.260721+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/dash.py", line 244, in _generate_scripts_html
2018-01-20T18:33:37.260722+00:00 app[web.1]:     self.scripts.get_all_scripts() +
2018-01-20T18:33:37.260720+00:00 app[web.1]:     self._generate_scripts_html()
2018-01-20T18:33:37.260723+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/resources.py", line 123, in get_all_scripts
2018-01-20T18:33:37.260725+00:00 app[web.1]:     return self._resources.get_all_resources()
2018-01-20T18:33:37.260726+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/resources.py", line 56, in get_all_resources
2018-01-20T18:33:37.260727+00:00 app[web.1]:     self.get_inferred_resources() + self._resources
2018-01-20T18:33:37.260728+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/resources.py", line 84, in get_inferred_resources
2018-01-20T18:33:37.260730+00:00 app[web.1]:     for t in layout.traverse():
2018-01-20T18:33:37.260736+00:00 app[web.1]: AttributeError: 'NoneType' object has no attribute 'traverse'
2018-01-20T18:33:37.508440+00:00 app[web.1]: [2018-01-20 18:33:37,507] ERROR in app: Exception on /favicon.ico [GET]
2018-01-20T18:33:37.261361+00:00 app[web.1]: 10.35.194.85 - - [20/Jan/2018:18:33:37 +0000] "GET / hteeteeeepee/1.1" 500 291 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
2018-01-20T18:33:37.508443+00:00 app[web.1]: Traceback (most recent call last):
2018-01-20T18:33:37.508445+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
2018-01-20T18:33:37.508452+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/dash.py", line 554, in _setup_server
2018-01-20T18:33:37.508446+00:00 app[web.1]:     response = self.full_dispatch_request()
2018-01-20T18:33:37.508447+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/flask/app.py", line 1607, in full_dispatch_request
2018-01-20T18:33:37.508448+00:00 app[web.1]:     self.try_trigger_before_first_request_functions()
2018-01-20T18:33:37.508450+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/flask/app.py", line 1654, in try_trigger_before_first_request_functions
2018-01-20T18:33:37.508459+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/resources.py", line 56, in get_all_resources
2018-01-20T18:33:37.509234+00:00 app[web.1]: 10.35.194.85 - - [20/Jan/2018:18:33:37 +0000] "GET /favicon.ico hteeteeeepee/1.1" 500 291 "hteeteeeepeescolon-slash-slashfakenamequickdashdotherokuapp/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
2018-01-20T18:33:37.508453+00:00 app[web.1]:     self._generate_scripts_html()
2018-01-20T18:33:37.508460+00:00 app[web.1]:     self.get_inferred_resources() + self._resources
2018-01-20T18:33:37.508451+00:00 app[web.1]:     func()
2018-01-20T18:33:37.508458+00:00 app[web.1]:     return self._resources.get_all_resources()
2018-01-20T18:33:37.508468+00:00 app[web.1]: AttributeError: 'NoneType' object has no attribute 'traverse'
2018-01-20T18:33:37.508457+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/resources.py", line 123, in get_all_scripts
2018-01-20T18:33:37.508455+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/dash.py", line 244, in _generate_scripts_html
2018-01-20T18:33:37.508463+00:00 app[web.1]:     for t in layout.traverse():
2018-01-20T18:33:37.508456+00:00 app[web.1]:     self.scripts.get_all_scripts() +
2018-01-20T18:33:37.508461+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/dash/resources.py", line 84, in get_inferred_resources
2018-01-20T18:33:37.510592+00:00 heroku[router]: at=info method=GET path="/favicon.ico" host=fakename request_id=f27f69a1-6739-4c6d-97d8-c6cb637e9a0e fwd="161.69.123.11" dyno=web.1 connect=0ms service=5ms status=500 bytes=456 protocol=hteeteeeepees

So I can see it’s saying it’s failing to load the app there, most likely because the app.py in my file doesn’t actually have a layout (that’s in index.py described below).

I’ve structured the app as follows:

|- multi_app
|- apps
| |- global_page.py
| |- region_page.py
| |- department_page.py
| |- municipal_page.py
| |- pycache
|- app.py
|- index.py
|- init.py
|- pycache
|- Datafile1.csv
|- Datafile2.csv
|- Procfile
|- requirements.txt
|- password_text.py

The app words perfectly well locally. Here is the pertinent code for the files:

app.py

import dash
import pandas as pd
import datetime

# bring in the data

# Activities Data prep
df = pd.read_csv('Actividades_Data_2018-01-12.csv')
df['date'] = pd.to_datetime(df['fecha_de_inicio']).dt.strftime('%m/%Y')
df['count'] = 1

#print(municipio)

# Pillars Data prep
pf = pd.read_csv('test_item_pruebas.csv')
pf['month_year'] = pd.to_datetime(pf['date']).dt.strftime('%m/%Y')
pf['count'] = 1

# Get current date for axes
now = datetime.datetime.now()

# Define Link Stylesheet Dictionary
link_dict = {'margin': 5, 'color': '#fff', 'padding': '8px', 'text-decoration': 'none', 'font-size': '150%', 'text-transform': 'uppercase'}
title_link_dict = {'margin': 0, 'color': '#fff', 'padding': 0, 'text-decoration': 'none', 'font-size': '100%'}
list_style = {'list-style': 'none', 'display': 'inline-block', 'float': 'right'}
title_style = {'list-style': 'none', 'color': '#fff', 'margin': 5, 'padding': '0px', 'text-decoration': 'none', 'font-size': '200%'}


app = dash.Dash()
server = app.server
app.config.supress_callback_exceptions = True

index.py*

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


from app import app
from apps import global_page, region_page, department_page, municipality_page, four0four_page
from test import VALID_USERNAME_PASSWORD_PAIRS

auth = dash_auth.BasicAuth(
    app,
    VALID_USERNAME_PASSWORD_PAIRS
)
# App Layout

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 == '/':
        return global_page.layout
    elif pathname == '/apps/global_page':
        return global_page.layout
    elif pathname == '/apps/region_page':
        return region_page.layout
    elif pathname == '/apps/department_page':
        return department_page.layout    
    elif pathname == '/apps/municipality_page':
        return municipality_page.layout    
    else:
        return four0four_page.layout



# style the app with CSS

app.css.append_css({"external_url": "hteeteeeepeescolon-slash-slashcodepen.io/chriddyp/pen/bWLwgPdotcss"})


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

an example of one of the apps with just the imports - global.py

I’m only showing an example of what I’m importing just for brevity but each app has 6 different dash apps and they all use a different aggregation of the data, so I return the layouts/callbacks from each (e.g. global_page). I have no issue rendering it locally.

# Global Page for QuickDash

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import pandas as pd
from dash.dependencies import Input, Output
import datetime

from app import app, df, pf, now, link_dict, title_style, list_style, title_link_dict

Procfile

web: gunicorn app:server

password_text.py

VALID_USERNAME_PASSWORD_PAIRS = [["fake_user", "password"]]

requirements.txt

appnope==0.1.0
certifi==2017.7.27.1
chardet==3.0.4
click==6.7
dash==0.18.3
dash-auth==0.0.9
dash-core-components==0.15.0rc1
dash-html-components==0.7.0
dash-renderer==0.11.0
decorator==4.1.2
Flask==0.12.2
Flask-Compress==1.4.0
Flask-SeaSurf==0.2.2
idna==2.6
ipython==6.2.1
ipython-genutils==0.2.0
itsdangerous==0.24
jedi==0.11.0
Jinja2==2.9.6
jsonschema==2.6.0
jupyter-core==4.3.0
loremipsum==1.0.5
MarkupSafe==1.0
nbformat==4.4.0
numpy==1.13.1
pandas==0.20.3
parso==0.1.0
pexpect==4.3.0
pickleshare==0.7.4
plotly==2.0.15
prompt-toolkit==1.0.15
ptyprocess==0.5.2
Pygments==2.2.0
python-dateutil==2.6.1
pytz==2017.2
requests==2.18.4
simplegeneric==0.8.1
six==1.11.0
traitlets==4.3.2
urllib3==1.22
wcwidth==0.1.7
Werkzeug==0.12.2
gunicorn==19.7.1

So initially I’m thinking the reason is won’t load is because I know Heroku looks for an “app.py” file and runs that and if it’s doing that, it’s not going to get the layout/logic (which is in index), but if I just give these files different names (and fix the import names correctly) by switching to index.py to app.py and app.py to base_app.py, I get a different error (posted below).

error when switching names to make index.py now have the name of app.py and app.py have the name base_app.py (with the imports sorted out properly for the file names)

(dash_apps) RTI-102626:app_base cinkpen$ git add .
(dash_apps) RTI-102626:app_base cinkpen$ git commit -m "testing base_app nonsense"
[master 7568ad7] testing base_app nonsense
 7 files changed, 6 insertions(+), 6 deletions(-)
 rename index.py => app.py (97%)
 rename app.py => base_app.py (100%)
(dash_apps) RTI-102626:app_base cinkpen$ git push heroku master
Enter passphrase for key '/Users/cinkpen/.ssh/id_rsa': 
Enter passphrase for key '/Users/cinkpen/.ssh/id_rsa': 
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 1.27 KiB | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Python app detected
remote: -----> Installing requirements with pip
remote: 
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: 
remote: -----> Compressing...
remote:        Done: 96.6M
remote: -----> Launching...
remote:        Released v8
remote:        hteeteeeepeescolon-slash-slashfakenamequickdashdotherokuapp/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To heroku:fakenamequickdashdotgit
   12a3da5..7568ad7  master -> master
(dash_apps) RTI-102626:app_base cinkpen$ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free

Then when I load up the app on heroku, it gives me an application error with the following logs:

(dash_apps) RTI-102626:app_base cinkpen$ heroku logs

2018-01-20T22:59:54.513314+00:00 app[web.1]: Failed to find application: 'app'
2018-01-20T22:59:54.517704+00:00 app[web.1]: [2018-01-20 22:59:54 +0000] [9] [INFO] Worker exiting (pid: 9)
2018-01-20T22:59:54.748030+00:00 app[web.1]: Failed to find application: 'app'
2018-01-20T22:59:54.752363+00:00 app[web.1]: [2018-01-20 22:59:54 +0000] [8] [INFO] Worker exiting (pid: 8)
2018-01-20T22:59:54.865229+00:00 app[web.1]: [2018-01-20 22:59:54 +0000] [4] [INFO] Shutting down: Master
2018-01-20T22:59:54.865366+00:00 app[web.1]: [2018-01-20 22:59:54 +0000] [4] [INFO] Reason: App failed to load.
2018-01-20T22:59:54.965234+00:00 heroku[web.1]: State changed from up to crashed
2018-01-20T22:59:54.951614+00:00 heroku[web.1]: Process exited with status 4
2018-01-20T23:01:04.178613+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=fakenamequickdashdotherokuapp request_id=8df411e9-471f-4947-9d1f-79d3f275e9c9 fwd="161.69.123.11" dyno= connect= service= status=503 bytes= protocol=hteeteeeepees
2018-01-20T23:01:04.428823+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=fakenamequickdashdotherokuapp request_id=d6a0e17d-a08e-4a13-ac28-586c8669667c fwd="161.69.123.11" dyno= connect= service= status=503 bytes= protocol=hteeteeeepees

I’ve also just moved the stuff from index into app.py to see what happens if I just load it up like that and then the app doesn’t render the pages “region_page.py”, “department_page.py”, and “municipal_page.py” and when I do it that way I sort out the issues with the circular dependencies.

So I’m just wondering if you can provide some feedback on how to deploy a multi-page app structured like the one on the dash tutorial (which I know is also a multi-page app) to Heroku.

Thanks!

2 Likes

Also, just as a note, I know there are some incongruities in the way I have the files there. For example, in the file structure, it’s supposed to be _init_.py and _pycache_.py but I messed up the markdown. Also, when I import the password info in index.py, I have the actual file as test.py (I just labeled it as password_text.py so you could understand why it was there). And the datafile1/datafile2 were just examples, I know the names look different in the actual appdotpy script (plotly keeps stopping me from posting my file names, jeez!).

Thanks.

Just a starting point, but when I had issues deploying, including these 3 lines in app. py cleared everything up:

app = dash.Dash(__name__)
server=app.server
server.secret_key = os.environ.get('secret_key', 'secret')

You have the app.server, but perhaps try including the other two.

4 Likes

Emunson, many thanks. This worked along with some other things I had to sort out (listed below):

  • had to import the server into my index file
  • modify the Procfile to specify index:server (instead of app:server)
    web: gunicorn index:server
  • add the secret_key line you mentioned

With this tweaks, I was able to deploy without an issue. Huge thanks and potentially some of this information should be included in the Dash documentation for the Multi-Page App section and/or the Heroku Deployment section.

4 Likes

This thread was an absolute life saver!

Thanks for the pointers Emunson and CSInkpen, the changes you outlined worked perfectly for deployment to IBM’s Cloud too.

I definitely think some consideration should be made for including this information in a multi-page deployment guide.

1 Like

I was troubleshooting for hours before finding this age.

Same, would it be possible to add this to the docs as a note here: https://dash.plot.ly/urls ?

1 Like

Thank you so much for this thread - saved me hours! This is important information

agreed this is so important! stumbled on this post to fix my deployment issues. It would be great to add this info to the https://dash.plot.ly/urls page.

This worked for me too! Thanks so much.