General guidelines#

Callbacks mechanism#

Callbacks are functions that are automatically called by Dash whenever an input component’s property changes. This is basically how we create a dynamic application from user inputs.

A callback is written in its general form as follow:

@app.callback(
    Output(component_id='my-output', component_property='children'),
    Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
    return 'Output: {}'.format(input_value)

This callback can be written in a more simple form by omitting the arguments like this:

@app.callback(
    Output('my-output', 'children'),
    Input('my-input', 'value')
)
def update_output_div(input_value):
    return 'Output: {}'.format(input_value)

This callback will fire whenever the value property fo the my-input element is changed and will update the children property of my-output element.

Note

The name of function arguments can be completely arbitrary. Only the order of Input and State arguments matters.

Prevent an update#

Sometimes you do not want to update an Output when a callback fires based on a logic you implement within the function. In that case, Dash provides the PreventUpdate exception you can raise to prevent an update:

@app.callback(
    Output('my-output', 'children'),
    Input('my-input', 'value')
)
def update_output_div(input_value):
    if input_value == None:
        raise PreventUpdate
    return 'Output: {}'.format(input_value)

Another option is to use dash.no_update directly as return value to prevent only a specific Output to update:

@app.callback(
    Output('my-output1', 'children'),
    Output('my-output2', 'value'),
    Input('my-input', 'value')
)
def update_output_div(input_value):
    if input_value == None:
        return 1, dash.no_update
    return None, None

Note

You may sometimes see code with the Output and Input in brackets like this:

@app.callback(
    [Output('my-output1', 'children'),
     Output('my-output2', 'children')],
    [Input('my-input1', 'value'),
     Input('my-input2', 'value')]
)
def func(input1, input2):
    return None, None

This is no more necessary since an update of Dash and the brackets can be omitted even with multiples Input or Output.

However you can still create multiples Input or Output in a row by using python list comprehension, and thus, using brackets in the callback arguments in this case is relevant.

@app.callback(
    [Output(f"my-output-{n}", 'children') for n in range(5)],
    Input('my-input', 'value')
)
def func(input):
    return None, None

Another option for the callback arguments are the State input. This latter acts as input but does not fire the callback when the value of the its property change. Thus, use the State input whenever you need an “indirect” information inside your function that must not fire the callback when its value changes.

@app.callback(
    Output('my-output', 'children'),
    Input('my-input', 'value'),
    State('my-state', 'data')
)
def update_output_div(input_value, state_value):
    return 'Output: {}'.format(state_value)

Use Dash extensions#

The dash-extensions package brings various extensions to the Plotly Dash framework and should be used every times.

To add the enrichments to the app, instead to create the Dash app like this:

app = dash.Dash()

Write it like this:

from dash_extensions.enrich import DashProxy, TriggerTransform, GroupTransform, \
    ServersideOutputTransform, NoOutputTransform

app = DashProxy(transforms=[
    TriggerTransform(),  # enable use of Trigger objects
    GroupTransform(),  # enable use of the group keyword
    ServersideOutputTransform(),  # enable use of ServersideOutput objects
    NoOutputTransform(),  # enable callbacks without output
])

TriggerTransform#

With TriggerTransform, instead to write this:

@app.callback(Output("output_id", "output_prop"), Input("button", "n_clicks"))
def func(n_clicks):

You write that:

@app.callback(Output("output_id", "output_prop"), Trigger("button", "n_clicks"))
def func():  # note that "n_clicks" is not included as an argument

Important

Note that the Trigger input does not work when we mix it with Input and State. Then, a Trigger is only useful for very simple callback.

NoOutputTransform#

This seems nothing, but the NoOutputTransform will save you to write dummy Output when you don’t really need to update an output with your callback. Since originally in Dash, a callback must have at least one Output and one Input.

You will write cleaner code with this enrichment.

@app.callback(Trigger("button", "n_clicks"))  # note that the callback has no output

GroupTransform#

One of the biggest pain with Dash is that we cannot use the same Output in multiples callbacks, which force us to write sometimes very large callback function. Thanks to the GroupTransform enrichment, we CAN use the same Output in multiples callbacks:

@app.callback(Output("log", "children"), Trigger("left", "n_clicks"), group="my_group")
def left():
    return "left"

@app.callback(Output("log", "children"), Trigger("right", "n_clicks"), group="my_group")
def right():
    return "right"

Important

This new feature will bundle callbacks together, thus to make it work properly, the number of outputs of the callback functions must be the same.

ServersideOutputTransform#

This new output works like a normal Output but it keeps the data on the server, reducing the network overhead. This is particularly useful when we work with more complex objects such as a pandas DataFrame and we can return the DataFrame directly without having to serialize this latter to JSON.

@app.callback(ServersideOutput("store", "data"), Trigger("left", "n_clicks"))
    def query():
        return pd.DataFrame(data=list(range(10)), columns=["value"])

Use the cache#

We can use the cache and memoize the results of a callback function. Memoization stores the results of a function after it is called and re-uses the result if the function is called with the same arguments.

The cache is not directly related to Dash, but Flask. Thus, we have to install the package flask_caching. Once installed, we can enable the cache with this simple code:

from flask_caching import Cache

...

CACHE_CONFIG = {
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': 'cache-directory',
}

cache = Cache()
cache.init_app(app.server, config=CACHE_CONFIG)

with app.server.app_context():
    cache.clear()

For more information about Flask caching configuration, consult the package documentation.

In the case above we tell Flask to store the cache in a local folder called cache-directory.

Then, to memoize the result of a callback we use the decorator @cache.memoize() as follow:

@app.callback(
    Output('flask-cache-memoized-children', 'children'),
    Input('flask-cache-memoized-dropdown', 'value'))
@cache.memoize()
def render(value):
    return 'Selected "{}" at "{}"'.format(
        value, datetime.datetime.now().strftime('%H:%M:%S')
    )

For more information about the performance of the application, consult the dedicated official Performance page.

Dash deployment#

When we invoke app.run_server(), behind Dash creates a Flask server instance. We can pass Flask server arguments directly to that function if we want to change some configuration of the Flask server.

In a standard deployment of a Dash application, on a UNIX platform, it is recommended to use gunicorn which is a python WSGI HTTP Server for Unix.

To deploy with gunicorn:

pip install gunicorn
gunicorn -b 0.0.0.0:8050 index:app.server

Assuming that the main entry of your application is index.py and that the file contain the app instance.

On a Windows platform, one can deploy a Dash application with waitress but it requires you to write little extra code in the index.py:

from waitress import serve
serve(app.server, host='0.0.0.0', port=8050)

Python Plotly Dash