Splitting callback definitions in multiple files

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