🚀 New plugin: Dash Event Callback

Hey community!

After introducing event callbacks in the latest version of Flash I am pleased to release the same for native Dash with the dash-even-callback plugin. Use this blog post 🚀 Flash 1.2.0 - streaming UI updates via Server Sent Events to checkout examples and details on how to implement and use dash_event_callback.
That said - it doesn’t support endless streams, has an overall 60s timeout - thats because Flask as WSGI server blocks a whole worker (e.g. deployed with gunicorn) for each request. So if your deployed container has 6 workers a single with 6 tabs opened could block the whole instance. But within the duration of normal callback you have no drawbacks using event_callback and benefit from streaming props as they become ready!

Looking forward to your feedback!

Hi @Datenschubse

That looks awesome, could you give simple examples of what you can do with this that you couldn’t really do with basic Dash polling ?

Hi @spriteware

Good question! But it’s actually not about what it can do - what Dash polling / background callbacks can’t, more about being able to do the same without the need of background callback and and its dependencies eg. Redis & Celery. Lets consider the simple progress update example from the background callback docs:

event_callback implementation:

from dash import Dash, html, Input
from dash_event_callback import event_callback, stream_props
import time

app = Dash(__name__)

app.layout = html.Div(
    [
        html.Div(
            [
                html.P(id="paragraph_id", children=["Button not clicked"]),
                html.Progress(id="progress_bar", value="0", hidden=True),
            ]
        ),
        html.Button(id="button_id", children="Run Job!"),
        html.Button(id="cancel_button_id", children="Cancel Running Job!", hidden=True, disabled=True),
    ]
)

@event_callback(
    Input("button_id", "n_clicks"),
    cancel=[(Input("cancel_button_id", "n_clicks"), 0)],
    reset_props=[
        ("progress_bar", {"hidden": True}),
        ("cancel_button_id", {"hidden": True, "disabled": True}),
        ("button_id", {"disabled": False}),
    ]
)
def update_progress(n_clicks):
    n = 5
    yield stream_props([
        ("progress_bar", {"hidden": False}),
        ("cancel_button_id", {"hidden": False, "disabled": False}),
        ("button_id", {"disabled": True}),
    ])
    time.sleep(1)
    for i in range(1, n):
        yield stream_props([
            ("progress_bar", {"value": str(i), "max": n}),
            ("paragraph_id", {"children": [f"Progress: {i * 20}%"]}),
        ])
        time.sleep(1)

    yield stream_props([
        ("paragraph_id", {"children": [f"Job Completed! Button clicked {n_clicks} times."]}),
        ("cancel_button_id", {"hidden": True, "disabled": True}),
        ("progress_bar", {"hidden": True, "value": "0"}),
        ("button_id", {"disabled": False}),
    ])

app.run(debug=True, port=11111)

In order to run this on a multi core setup and background callbacks, you would be required to run at least 3 containers - Dash, Redis & Celery. With event callbacks you just need Dash.

Another nice but subtle advantage is that you are less prone to bad internet connections. Every poll requires the complete http process, while the sse just keeps the connection open. So even if you throttle to 3G via the network tab, after the first byte got send, there is no delay between the updates.

That said. this actually only applies if your event_callback stays in the timeframe of a regular callback. If your callback has a longer exec time then 30 secs you would still be required to use background callbacks with Celery.

Update 1.2.0

  • fixed: request prefixes of your application get acknowledged
  • streaming timeout can be set - default 30sec
  • setup_event_callback needs to be called in your main app file
from dash import Dash
from dash_event_callback import setup_event_callback, event_callback, stream_props

timeout = 60 # Custom timeout of 60 secs

setup_event_callback(timeout=timeout)

app = Dash(__name__)

... 

Great, thank you for the example.
Regarding the timeout, can it be infinite streaming if we set the gunicorn timeout to 0 (infinite)?

Sure thing!

You can now set the timeout as you like, and Gunicorn’s timeout won’t cut off the stream prematurely. That said, you can’t set it to run endlessly. If you need that, consider using Flash, since event streams put far less load on the system. The blog post on Flash includes visualizations that show the performance impact across different servers.