📣 Dash 2.6 Released - Background Callbacks, Unit Testing, Persistent Selections, Dropdown Features

We’re excited to announce that Dash 2.6.0 & 2.6.1 have been released :rocket: 2.6.1 contains a small bug fix for one of the features in 2.6.0. These are a backwards compatible releases and we recommend going straight to 2.6.1 .

pip install dash==2.6.1

Official Changelog :arrow_forward: Dash v2.6.0 and Dash v2.6.1

Highlights :arrow_down_small:

:tv: What’s new in Dash 2.6
new-in-dash-image

Background Callbacks

Background Callbacks are a rewrite of the long_callback feature launched in Dash 2.0. To preserve backwards compatibility with long_callback, we updated the syntax to callback(..., background=True). If your app already uses long_callback then you don’t need to change anything. background=True resolves several outstanding limitations of long_callback including incompatibility with pattern-matching callbacks. Many thanks to everybody’s feedback on this!

Background callbacks run your callbacks in a background queue, enabling your apps to scale to more concurrent users and preventing your apps from running into network timeout issues.

Consider an app that has a callback that takes 10 seconds to run. Every time that callback is fired, a worker (CPU) is allocated to crunch the numbers. If your app is deployed with 4 CPU workers and if 4 people visit your app and execute the same callback at the same time, your app will have no more workers to serve the 5th app visitor. As a result, the 5th visitor will see a blank page for up to 10 seconds until the first worker becomes available.

Background callbacks offer a scalable solution for using long-running callbacks by running them in a separate background queue. In the background queue, the callbacks are executed one-by-one in the order that they came in by dedicated queue worker(s). The web workers remain available to load the dash app, return results of the background callback, and run non-background callbacks.

To run a callback in the background, set background=True inside your callback decorator and pass a background callback manager to the dash.Dash app constructor.

app.py

import time
import dash
from dash import DiskcacheManager, CeleryManager, Input, Output, html
import os

if 'REDIS_URL' in os.environ:
    # Use Redis & Celery if REDIS_URL set as an env variable
    from celery import Celery
    celery_app = Celery(__name__, broker=os.environ['REDIS_URL'], backend=os.environ['REDIS_URL'])
    background_callback_manager = CeleryManager(celery_app)
else:
    # Diskcache for non-production apps when developing locally
    import diskcache
    cache = diskcache.Cache("./cache")
    background_callback_manager = DiskcacheManager(cache)

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

app.layout = html.Div([
    html.Div([html.P(id="paragraph_id", children=["Button not clicked"])]),
     html.Button(id="button_id", children="Run Job!"),
])

@dash.callback(
    output=Output("paragraph_id", "children"),
    inputs=Input("button_id", "n_clicks"),
    background=True,
)
def update_clicks(n_clicks):
    time.sleep(2.0)
    return [f"Clicked {n_clicks} times"]


if __name__ == "__main__":
    app.run_server(debug=True)

Background callbacks leverage the open source Celery library and the open source Redis database under the hood. In Dash, we’ve abstracted away the complexity of stitching together these technologies into the simple background=True API :tada:

Deploying Apps with Background Callbacks

To deploy an app with background callbacks, you’ll need:

  1. A deployment platform that can run two commands:
    • gunicorn app:server --workers 4 - For running the Dash app and “regular” callbacks. In this case, 4 CPU are serving these requests.
    • celery app:server --concurrency=2 - For running the background job workers. In this case, 2 CPU will be running the background callbacks in the order that they are submitted.
  2. A Redis database available with network access available to both of those commands. Dash will submit and read jobs to and from Redis.

If you’re using Dash Enterprise (or Heroku), then run these two commands by creating a Procfile with the commands:

web: gunicorn app:server --workers 4
queue: celery app:server --concurrency=2

and add Redis to your app and workspace development environment by clicking the “Add Redis” button in the UI:

DiskCache in Development

If you don’t have Redis available in your local development or testing environment, then you can use Dash’s DiskCache manager instead of Redis. This manager won’t actually run the callbacks in a queue but it will enable you to use the background=True syntax for development.

If you’re using Dash Enterprise, then Redis is available for development within a Dash Enterprise workspace.

Progress Bar & Start/Stop Buttons

Background callbacks provide some additional features to callbacks like progress bars that can be updated programmatically (instead of the generic dcc.Loading component) and start/stop/disabled buttons.

long-callbacks-example-4

Caching

Additionally, background callbacks provide a cache_by argument to store the results of the background callback and return it immediately upon subsequent calls:

callback(..., background=True, cache_by=True)

Docs

:arrow_right: Background Callback

:arrow_right: Background Callback Caching

:tv: Deep dive into Background Callbacks with myself & @chriddyp
background-callback-image

Dropdown Search & Height

Search

The new search keyword can be used to provide richer results in the dcc.Dropdown. @prokie, thank you for reporting the bug that led us to create this new feature :pray:

In Dash v2.5.0 we introduced components as option labels. By default, the value will be used when a user would search for a dropdown option.

With Dash v2.6, you can assign a string to the search keyword for each option. For example, in the first option, the search string is equal to the label (“Montreal”), and in the second option the search string (“The Big Apple”) is completely different from the value and label .

dcc.Dropdown([
        {
            "label": html.Div(['Montreal']),
            "value": "MTL",
            "search": "Montreal"
        },
        {
            "label": html.Div(['New York City']),
            "value": "NYC",
            "search": "The Big Apple"
        }
    ],
    value='Montreal'
)

dropdown-search

Max Height

We’ve added the maxHeight prop for the Dropdown options menu. Thank you @AnnMarieW :pray:
Prior to Dash v2.6, the dropdown options would automatically display inside a vertical scrollbar if you had more than 6 options since the default height of the dropdown menu is 200px.

Now, you can assign an integer (representing pixels) to the new maxHeight prop to show a larger number of select options, without any vertical scroll bar appearing. For example, let’s make the options area 800px instead of the default 200px.

from dash import Dash, html, dcc
import plotly.express as px

app = Dash(__name__)

app.layout = html.Div([
    dcc.Dropdown(
        [
            'Option-1', 'Option-2', 'Option-3', 'Option-4', 'Option-5', 'Option-6', 'Option-7', 'Option-8', 'Option-9'
        ], 
        value='Option-2',
        maxHeight=800
    )
])

if __name__ == "__main__":
    app.run_server(debug=True)

drop-more-options

Docs

:arrow_right: Dropdown Search

:arrow_right: Dropdown Height


Unit Testing of Callbacks

Testing is an essential component of production-grade software development. In Dash 2.6, we’ve made it easier for you to write unit tests of callbacks. Unit tests are used to test individual functions of your Dash app whereas end-to-end tests spin up a real browser session and click through the UI. Many robust software projects will have a combination of unit and end-to-end tests. Unit tests are easier to set up and quicker to run than end-to-end tests.

Unit testing in Dash now looks like:

app.py

# ...

@callback(Output('div', 'children'), Input('input', 'value'))
def update(value):
    return f'You have entered {value}'

test.py

 from app import update

 def test_update_callback():
     output = update('hello world')
     assert output =='You have entered hello world'

and then run the test in your terminal with:

$ pytest

Docs

:arrow_right: Unit Testing in Dash


Persistent Selections

With Dash 2.6.0, we’ve made two improvements to the Box Select and Lasso Select tools in the mode bar:

Persistent Selections: Previously, the selection outline would disappear after you’ve made the selection. Now the selection box or lasso will persist. This makes it easier to view the currently selected region and adjust the selection.

Pre-select: Selections can be added to a dcc.Graph that displays to the app user when it loads. These can also be moved or resized.

In the example below, we use add_selection to pre-populate a rectangle type="rect" within a set of points on the graph:

from dash import Dash, dcc, html
import plotly.express as px

app = Dash(__name__)

df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color='petal_length')

fig.add_selection(type="rect", x0=3.0, y0=6.5, x1=3.5, y1=5.5)

fig.update_layout(dragmode='select',
                  newselection=dict(line=dict(color='blue')))

app.layout = html.Div([
    dcc.Graph(figure=fig)    
])


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

In the example, we add the second Box selection by holding SHIFT + Left click

add-selections

Docs

:arrow_forward: Add selections


Plotly.py

The version of Plotly.js that is built in here is the same one that is bundled with the recently released Plotly.py 5.10.0, so we recommend that you upgrade to Plotly 5.10.0 to get the full benefit of all of these libraries working together.

pip install plotly==5.10.0

Official Changelog :arrow_forward: Plotly v5.10.0

Notable Bug Fixes & Minor Changes

  • Update dcc.Graph to use Plotly.js to v2.13.3 (from v2.12.)
    • Fix sankey select error
    • Add unselected line styling to parcoords traces.
    • Add more quartile algorithms to violin traces.
    • More flexible axis automargin behavior.
    • And several other enhancements and bug fixes.
  • #2126 Fix bug where it was not possible to redirect from root when using pages. Thank you @AnnMarieW :green_heart:
  • #2114 Fix bug #1978 where text could not be copied from cells in tables with cell_selectable=False. Thank you Benedikt :raised_hands:
  • #2102 Fix bug as reported in dash-labs #113 where files starting with . were not excluded when building dash.page_registry. Thank you Charmeem for reporting and thank you @AnnMarieW Ann for the PR :pray:
  • #2100 Fixes bug where module name in for a custom not_found_404 page is incorrect in the dash.page_registry when not using the pages folder. Thank you Jacobs :heart_hands:
  • #2098 Accept HTTP code 400 as well as 401 for JWT expiry
  • #2097 Fix bug #2095 with TypeScript compiler and React.FC empty valueDeclaration error & support empty props components. Thank you Junsung for reporting :slight_smile:
  • #2104 Fix bug #2099 with Dropdown clearing search value when a value is selected. Thank you Sergiy for reporting :innocent:
  • #2039 Fix bugs in long callbacks: Thank you sazzamac, kiwifoxtrot, and Samuel :sunglasses:
    • Fix #1769 and #1852 short interval makes job run in loop.
    • Fix #1974 returning no_update or raising PreventUpdate not supported with celery.
    • Fix use of the callback context in celery long callbacks.
    • Fix support of pattern matching for long callbacks.
  • #2110 Fix dcc.Dropdown search with component as prop for option label.
  • #2131 Fix bug #2127 - Add encoding to file open calls. Thank you Luca for reporting :orange_heart:
  • #2116 Rename long callbacks to background callbacks
    • Deprecated dash.long_callback.managers.CeleryLongCallbackManager, use dash.CeleryManager instead.
    • Deprecated dash.long_callback.managers.DiskcacheLongCallbackManager, use dash.DiskcacheManager instead.
    • Deprecated dash constructor argument long_callback_manager in favor of background_callback_manager.

Previous Releases

:mega: Dash 2.5 Released - Easier Multi-Page Apps, Component Properties
:mega: Dash 2.4 Released - Improved Callback Context, Clientside Callback Promises, Typescript Components, Minor Ticks, and More!
:mega: Dash 2.3.0 Release - MathJax and fillpattern option in scatter trace
:mega: Dash 2.2.0 Release - Adds ticklabelstep to axes, and added dash.get_asset_url
:mega: Dash 2.1.0 Release - Autogenerated IDs and reärranged keyword arguments in Dash components

10 Likes

I’m very excited about the addition of unit testing. I ran into an issue while trying to get it to work with the Pages functionality. When I import the tests, I see the following error:

src\pages\home.py:5: in <module>
    dash.register_page(
.venv\lib\site-packages\dash\_pages.py:253: in register_page
    _validate.validate_use_pages(CONFIG)
.venv\lib\site-packages\dash\_validate.py:465: in validate_use_pages
    raise Exception("`dash.register_page()` must be called after app instantiation")
E   Exception: `dash.register_page()` must be called after app instantiation

Is there a way to ignore this error while running tests?

Is there a way to ignore this error while running tests?

You can try except the import of the callback in the test file:

try:
    from pages.my_page import my_callback
except:
    pass

It’s a bare exception so it will catch any error that happens during import, I opened an issue to change that to a custom exception.

4 Likes

This update is really awesome, I tried it out on my machine and it’s very straightforward!

One question though- my design is to have a long_callback’s result be checked on every 750 ms or so by an Interval component. This is nice because from the client perspective, the server is super responsive.

With this update will the client/server polling be handled by the CeleryManger? i.e. if I have a really long callback do I still ever need to check the job’s status or is that now handled by the manager?

Subsequently, how is Loading handled? How should we use the loading_state argument to our clientside components now that the background_callback is assumed to be running in the background for a while rather than returning immediately and setting the loading state after adding a job to the queue?

Thanks so much for this update in any case, it’s awesome!

1 Like


A simple example code is showing updates a long time

I have run celery and Redis server in my ubuntu, but when I click the button it shows a long-time update.
is the has anyone done this simple example code?
thanks
Saddam


if I use background = True then on the network auto loading dash-update-component.
how could I stop that task?

The update is really awesome. However, if I wish to disallow the new persistent selection feature. Is there any way to specify that?

hi @EZLearner
What exactly do you mean by “disallow the new persistent”? What exactly do they not want to see?

Lovely work all, especially on the pages front… alas, it appears that the dash-auth package has been left quite out-of-date now and outright broken with the latest updates… any plans to refresh this?

Hi @nickdal and welcome to the Dash community :slight_smile:

Yes, the dash-auth package is out of date, but the basic auth works with pages

You can find an example from the dash-multipage-app-demos repo.

The package hasn’t been updated for dash 2.0, so you will get warning messages about the old style import statements. The deprecation messages can be ignored or suppressed until the package gets updated.

1 Like