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)