Dash callback in a separate file

In the first example at
https://dash.plot.ly/getting-started-part-2

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

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(id='my-id', value='initial value', type='text'),
    html.Div(id='my-div')
])


@app.callback(
    Output(component_id='my-div', component_property='children'),
    [Input(component_id='my-id', component_property='value')]
)
def update_output_div(input_value):
    return 'You\'ve entered "{}"'.format(input_value)


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

is it possible to have the callback portion in another file? My app works fine if the call back is also in the same file, but Iā€™d like to separate it out for clarity.

Thanks

2 Likes

We would also like to be able to do this, as we could better organize our code. E.g. we would like to create a file/folder structure similar to:

  • app.py
  • layouts/
    • main_layout/
      • layout.py
      • callbacks.py

When we try importing app into callbacks.py, so we can attach the callbacks to the app instance, the app seems to be a new instance or at least has not yet been instantiated with the layout.

I have been trying the same thing, and it is very frustrating. The callbacks do not seem to fire when I attach them to an imported app instance.

For what itā€™s worth, the slapdash boilerplate project separates callbacks into their own file, but requires that the Dash project be installed as a Python package.

It would be really nice if the Dash guide could document how to organize files in a cleaner way than putting everything into a single file.

This question was raised again in a different thread, and will hopefully be addressed soon.

1 Like

Hopefully we can get some technical assistance with this question from one of these issues:

https://github.com/plotly/dash-recipes/issues/19
https://github.com/ned2/slapdash/issues/6

Hey everyone,

Iā€™ve been learning Dash over the past few days and am going through the tutorial. As Iā€™ve done more and more callbacks, I realized app.py file was getting way too big. Iā€™m afraid that in a real-case scenario, having the callbacks within app.py file might be a mess, so Iā€™d like to have the following architecture:
app.py that contains the main app and its layout
callbacks1.py that contains some callback functions
callbacks2.py same thing

Iā€™ve ALMOST managed to make it work. Here is my code:
app3.py

# -*- coding: utf-8 -*-

################################################################################
###
### IMPORTS
###
################################################################################
import os
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd




################################################################################
###
### SETUP
###
################################################################################
path = os.path.dirname(os.path.abspath(__file__))
os.chdir(path)
app = dash.Dash(__name__)
df3 = pd.read_csv('data/test_data_3.csv', sep='|')



################################################################################
###
### LAYOUT
###
################################################################################
app.layout = html.Div([
    html.Div([
        dcc.Graph(id='graph-with-slider'),
        dcc.Slider(
            id='year-slider',
            min=df3['year'].min(),
            max=df3['year'].max(),
            value=df3['year'].max(), # Base value
            marks={str(year): str(year) for year in df3['year'].unique()},
        ),
    ]),
])



################################################################################
###
### CALLBACKS
###
################################################################################
import cbs3



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

cbs3.py

import plotly.graph_objs as go
import dash.dependencies as dd
from app3 import app, df3

@app.callback(
    dd.Output('graph-with-slider', 'figure'),
    [dd.Input('year-slider', 'value')]
)
def update_with_slider(selected_year):
    """ Update the figure property of our graph based on the slider value, everytime it is modified """
    filtered_df = df3[df3.year == selected_year]
    traces = []
    for i in filtered_df.continent.unique():
        df_by_continent = filtered_df[filtered_df.continent == i]
        traces.append(go.Scatter(
            x=df_by_continent.gdpPercap,
            y=df_by_continent.lifeExp,
            text=df_by_continent.country,
            mode='markers',
            opacity=0.7,
            marker={
                'size':15,
                'line': {'width':0.5, 'color': 'white'}
            },
            name=i,
        ))
    # We return a dict that basically is the property for the dcc.Graph() element.
    return {
        'data': traces,
        'layout': go.Layout(
            xaxis={'type': 'log', 'title': 'GPD Per Capita',},
            yaxis={'title': 'Life Expectancy', 'range': [20, 90]},
            margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
            legend={'x': 0, 'y': 1},
            hovermode='closest',
        ),
    }

The weird thing is that: if I change ā€œimport cbs3ā€ into ā€œfrom cbs3 import *ā€, it works. If I leave "import cbs3ā€™, it doesnā€™t work. Iā€™d like to understand why and be able to fix it, as I know using ā€œimport *ā€ is not a good idea.

Any help would be greatly appreciated.
Thanks

2 Likes

See " Structuring a Multi-Page App" in https://dash.plot.ly/urls.

2 Likes

Thanks for the link. It is useful, but not quite what we are discussing.

The goal here is to separate a single app into several files, such as:

  • app.py - core app definition, CSS loading, import and attach layout
  • layouts.py - one or more layouts, could be a sub-module
  • callbacks.py - callback functions attached to app layout
  • routes.py - routing logic for a multi-page app
  • utils.py - utility functions

This type of file organization is common as projects grow in size, and can be seen in the slapdash boilerplate:

2 Likes

Just calling out this quote for reference.

The idea is to have a documented ā€˜best practiceā€™ for organizing medium to large Dash apps, so that not everything needs to be in app.py.

It is common for web frameworks, such as Django and perhaps Flask, to offer a common way to structure project files.

1 Like

In my experience creating large apps, itā€™s easier to have callbacks in each file with the layout. Often, callbacks are an essential part of the content that gets loaded into the particular page, so having them in a separate file isnā€™t as helpful.

Perhaps there could be two different suggestions in the guide then. Would you be interested in creating a PR in dash-docs with what you are proposing?

2 Likes

Thanks for the link! It is useful but it does not answer the problem Iā€™m mentioning. Every single time (even in your link), the callbacks are in the same file as the app layout (and everything else). Itā€™s easy when there are only 2 graphs and 2 callbacks, but when you reach 10 or 20, the file becomes a mess.

My wish would be to have callbacks and layouts in separate files and import them into the app, instead of having one gigantic file.

1 Like

Not exactly. In the link I shared, I propose separating out sections of your layout into other files but keeping the callbacks that are associated with that content in the same file or module. In that example, I show how to separate it out into 3 different files: index.py is your ā€œroutesā€ and it defines the initial app.layout. Then, each section of content and the associated files are in the different files (app/app1.py).

Of course, you could split this out further: the key is to have your index file import all of your files so that the callbacks are registered, and having your app declaration in a separate file (app.py: app = dash.Dash(__name__)) to prevent circular imports.

5 Likes

My bad, I saw your answer to Brylie after I posted mine.

Reading it and re-checking your link, what youā€™re saying makes sense and made me reconsider. When you put it that way, it does make sense to have layouts and callbacks together (as they are closely linked) and then split up the app like you said (file1: layout1+callback1, file2: layout2+callback2, etc.)

Iā€™m gonna give it a shot
Thanks for your time! It was very informative and helpful

1 Like

You can use a function pattern instead of import the app from a global in a file. Importing from global leads to circular imports issues depending on the on the os. Dash-docs got this problem, on windows it wonā€™t register the examples callbacks unless you change the imports.

Basically, instead of importing the app you take it as a parameter where you need it. In your main app file you import the function and call it with your app. This way you donā€™t import the app in multiple files.

my_app/layout.py

import dash_html_components as html

layout = html.Div([
    html.Button('Click me', id='btn'),
    html.Div(id='output')
])

my_app/callbacks.py

from dash.dependencies import Output, Input

def register_callbacks(app):

    @app.callback(Output('output', 'children'), [Input('btn', 'n_clicks')])
    def on_click(n_clicks):
        return 'Clicked {} times'.format(n_clicks)

app.py

import dash
from my_app.layout import layout
from my_app.callbacks import register_callbacks

app = dash.Dash
app.layout = layout
register_callbacks(app)
18 Likes

Hi,

After reading your post I decided to give it a shot, see if I could make it work.
Both my callbacks and layouts use pandas dataframes loaded in the ā€œsetupā€ phase, so I made a few adjusments:

  • register_callbacks takes an additional argument ā€˜dataframeā€™ so I can use it in my callbacks
  • layout is now a function that returns the html div and takes a dataframe as argument

Basically my app.py has become:

import pandas as pd
import dash
from my_app.layout import layout
from my_app.callbacks import register_callbacks

df1 = pd.read_csv('data/test_data_3.csv', sep='|')
app = dash.Dash
app.layout = layout(df1)
register_callbacks(app, df1)

If Iā€™m using several dataframes in a same layout/callbacks, then Iā€™ll have to pass them all as arguments. But it works!
Thanks a lot for your assistance

5 Likes

I borrowed this solution in my template for a full dash app inside a flask server.

Github repository with explanations at How to embed a Dash app into an existing Flask app.

Thanks for the solution and enjoy the template :slight_smile:

3 Likes

Lovely! Thanks @Philippe and @Jordan

I am still having issues, trying to put the layout of each page and its associated callbacks into one file, and have the dash app server initialized in a separate file. The callback in the index page needs to refer to dash app, but dash app also needs the layout be attached in the same file where the dash server is initialized, while the layout needs to include all pages, including the index page. So I receiving this error:

NoLayoutException: The layout wasNoneat the time thatrun_serverwas called. Make sure to set thelayoutattribute of your application before running the server.

Here is my brief layout. flask_app is the file contains the existing flask that I am adding dash into:

__init__.py
app.py
index.py
page1.py

__init__.py

import dash
from flask_app import app

dashapp = dash.Dash(__name__,
                     server=app,
                     url_base_pathname='/dash/',
                     assets_folder=get_root_path(__name__) + '/assets/')
					 
app.py

def serve_layout():
    if flask.has_request_context():
        return body_content
    else:
        return html.Div(children=[
            body_content,
            index_page_content,
            page1_content,
        ])
        
with app.app_context():
    dashapp.title = 'Dashapp'
    dashapp.layout = serve_layout()
	
index.py

body_content = dbc.Container(fluid=True, children=[ 
    dcc.Location(id='url', refresh=False),
    html.Div(children=[
        html.Div(id='page-content', children=[]),
    ]),
])


index_page_content = html.Div([
    html.Div([], className='container-fluid')
], className='main-content-inner')

 @dashapp.callback(Output('output', 'children'), [Input('btn', 'n_clicks')])
def on_click(n_clicks):
	return 'Clicked {} times'.format(n_clicks)
		
page1.py

page1_content = html.Div(children=[])
 
 @dashapp.callback(Output('output', 'children'), [Input('btn', 'n_clicks')])
def on_click(n_clicks):
	return 'Clicked {} times'.format(n_clicks)

Hey were you able to find solution for this?

I asked a related question on SO, and I think the suggested pattern works well. The idea is to

  1. Define your app instance in separate file and use it for importing it to add callbacks.
  2. Create separate file that starts the server with app.run_server()
2 Likes

I just asked this can you have a look at it?