Splitting callback definitions in multiple files

Hi,

My app is growing too large and I would like to be able to separate the callback definitions in separate files and import them in the main file. It would seem that they all need to have access to “app” so that “@app” works.

I’ve looked into using Blueprints from flask but that didn’t seem to work.

Is there a way at all?

3 Likes

This is a great question. Here’s my strategy for breaking out a Dash app into
manageable pieces:

I have a directory structure as follows:

run.py
myproject/
       __init__.py
       app.py
       server.py
       callbacks1.py
       callbacks2.py
       wsgi.py

__init__.py

empty file

app.py

from .server import app, server
from . import callbacks1
from . import callbacks2

app.layout = html.Div('hello world')

server.py

from flask import Flask
from dash import Dash

server = Flask('myproject')
app = Dash(server=server)

callbacks1.py

from .server import app

@app.callback(...)
def callback1(...):
    pass

callbacks2.py

from .server import app

@app.callback(...)
def callback2(...):
    pass

wsgi.py

from .app import server as application 

run.py

from myproject.app import app

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

In this setup, the Dash app is organised into a package called myproject,
which has a module app.py, which is the entry point to the app that imports
everything else. The actual Dash instance (app) itself is found in the server.py
module along with the Flask instance (server). The reason for this perhaps
confusing separation will become clear shortly.

The callbacks are defined in callbacks1.py and callbacks2.py and each need
to be imported in app.py so that they are registered with the Dash
instance. At the top of each of the callback modules, we need to import the Dash
instance from the server module. This is why we have to use separate app and
server modules: the app module imports each callback module, but if each
callback module immediately tried to import from the app module, we’d have a
circular dependency and the import chain would fail. So we break them up to
remove the circularity.

As for running the app, because it’s a package, we can’t use the
if __name__ == '__main__': entry point to run the server. So we can either use
the WSGI entry point with a WSGI server like gunicorn :

$ gunicorn myproject.wsgi

Or you can use the run.py script that uses the Flask server:

$ run.py

Both those commands assume you’re in the same directory as the myproject
directory. But we could also take things to the next level and make this package
installable
by creating a setup.py file at the top level of the project. Then, once installed,
the gunicorn command can be run from any directy (assuming the virtualenv you
installed your project has gunicorn installed and is activated).

Hopefully that all makes sense!

8 Likes

Informative post, but your strategy doesn’t work.

In the file “app.py” you cannot import the callback files 1 and 2 before defining the layout or you will get this error:

dash.exceptions.LayoutIsNotDefined:
 Attempting to assign a callback to the application but
 the `layout` property has not been assigned.
 Assign the `layout` property before assigning callbacks.
1 Like

isn’t this the error you get when not using?

app.config[‘suppress_callback_exceptions’] = True

this prevents the error (since callbacks are loaded before layout)

1 Like

Yeah, suppress_callback_exceptions should be set. The full error message indicates this as well:

                Attempting to assign a callback to the application but
                the `layout` property has not been assigned.
                Assign the `layout` property before assigning callbacks.
                Alternatively, suppress this warning by setting
                `app.config['suppress_callback_exceptions']=True`

Ah yes, well I’m not sure I want to suppress errors. I found a better solution. In the file app.py just import the callbacks after the layout, like the error message suggests:

app.py

 from .server import app, server

 app.layout = html.Div('hello world')
 from . import callbacks1
 from . import callbacks2
2 Likes

One more thing.

You need to do ALL the layout before loading the callbacks. PITA!
So you would need to split the layout from the callbacks then
import the layouts
perform the top level layout
import the callbacks

It sort of defeats the purpose of DASH, IMHO, because now you have stuff scattered all over.

How can a cache be shared between these callbacks?