==================
General guidelines
==================
.. contents::
:local:
:depth: 1
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:
.. code-block:: python
@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:
.. code-block:: python
@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:
.. code-block:: python
@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:
.. code-block:: python
@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:
.. code-block:: python
@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.
.. code-block:: python
@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.
.. code-block:: python
@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:
.. code-block:: python
app = dash.Dash()
Write it like this:
.. code-block:: python
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:
.. code-block:: python
@app.callback(Output("output_id", "output_prop"), Input("button", "n_clicks"))
def func(n_clicks):
You write that:
.. code-block:: python
@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.
.. code-block:: python
@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:
.. code-block:: python
@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.
.. code-block:: python
@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:
.. code-block:: python
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:
.. code-block:: python
@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``:
.. prompt:: bash
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``:
.. code-block:: python
from waitress import serve
serve(app.server, host='0.0.0.0', port=8050)
:tag:`Python`
:tag:`Plotly Dash`