Successfully Running Dash with Quart (asyncio web framework)

So I was listening to the latest episode of Talk Python to Me today, where I learnt about Quart, an asynco-based Flask-API compatible web framework. The claim was that it could be largely used as a drop in replacement for Flask and sees some pretty hefty performance increase on the number of requests processed per second. I figured I would test out the drop-in claim by dropping it into Dash, and after a bit of massaging managed to get it working!

I have not done any profiling (and probably won’t at this point), but I though others with scaling needs might be interested, so I’ve pushed the the code to a branch if anyone wants to play around. It’s pretty straightforward to get going, and I’ve included instructions in dash/async.py.

Notable limitations are that it requires Python 3.6 and gzip compression is turned off as the flask_compress library is not yet supported by Quart (but compression is best done by your web server anyway).

10 Likes

Hi, Nedned,

That’s really interesting. How can I install that? I have dash installed already.

I have some asyncio requests want to integrate into dash, but it always said concurrent.futures._base.CancelledError

The fork I managed to get working was more of an experimental thing, so it’s not currently installable. I had been thinking about whether it might be feasible to make this extensibility supported by Dash, but hadn’t gotten anywhere further.

The other option you could get some mileage out of is using async workers with gunicorn. I also see that there’s an AsyncIO worker type, which I didn’t know about.

Hi Nedned,
I tried to patch your support-quart code in the latest Dash source and ran into various runtime issues. (I’m a little wobbly with asyncio.)

Has there been any work on this front, or do you have any hints?

thanks!

I got this working by lazy-copying dash.py and making the appropriate changes. For those who want to play with Dash and asyncio, this is pretty easy. I ran this with the fertility rate, life expectancy, etc graph example, and the speed is promising (is it faster than flask? It seems faster to me… not sure how to get hard numbers.)

Be sure to install quart and quart-compress packages before running.

For example:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dashquart

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dashquart.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),

    html.Div(children='''
        Dash: A web application framework for Python.
    '''),

    dcc.Graph(
        id='example-graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': {
                'title': 'Dash Data Visualization'
            }
        }
    )
])

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

In these tests Quart can serve almost 3x as many requests per second than Flask https://hackernoon.com/3x-faster-than-flask-8e89bfbe8e4f

I diffed your changes with the current dash.py and they are very minimal. If these minimal changes are all that is required then I am strongly in favour of using Quart. Not only is it faster but it opens the door for websocket callbacks.

2 Likes

The downside to this is that calling blocking code inside your callback will suspend the whole server if you do not have enough workers.

Therefore callbacks themselves must be async and blocking code in a callback must be awaited. This imposes additional complexity on us as developers and creates many opportunities to shoot ourselves in the foot.

I had to make a small tweak to the callback wrapper for this to work but I set up two repl.its to demonstrate the aforementioned issue.

BAD - Block the server

Try typing in the first text box, then quickly typing in the second box. Changes to the second box will only happen once the first box has changed.

GOOD - Doesn’t block the server

What we can do to get around shooting ourselves in the foot is to always use async inside Dash and automatically convert synchronous callbacks to async.

All this means is just to check if the callback is a coroutine and then apply Andrew Godwin’s async_to_sync

if not inspect.iscoroutinefunction(func):
    output_value = await sync_to_async(func)(*args, **kwargs)  
else:
    output_value = await func(*args, **kwargs) 

Then the user can choose to write async or sync callbacks. Existing code should work in most cases. Maybe some thread sensitive code might fail.

Hi Stephen,
This is interesting :slight_smile: Flask by default handles each request in a separate thread. Your change reintroduces threads at the Dash layer.

Regarding thread sensitivity, since Flask has threaded requests, you can see these kinds of issues currently. (Out of order callback)

I’m curious how these changes affect speed, all things considered :slight_smile:

I played around with Quart and its built-in websocket support.

Here’s another version of dash.py (dashmq.py). It allows you to modify Dash components programmatically, no callback needed.

It adds a modify() function to the Dash object.

I had another version of this that used flask-socketio as the back-channel. It required eventlet threading and monkey-patching to work. This version is progress (I think)…

Run the the demo by running ‘python3.7 usage.py’.

This is similar to what I was thinking. But I would prefer not to directly set values of the components. Instead I suggest making an update call. Something along the lines of:

def long_running_function():
    time.sleep(2) # e.g. database query or waiting for new data from event
    app.update(component_ids = ['progress'])

The frontend (javascript in browser) would listen for update requests. Once an update is received it would then trigger the required callbacks from the server, similar to how UI interactions trigger callbacks.

The reason is that components often depend on the UI state in the browser, which the server cannot see.

An update function like you describe would create a list of callbacks associated with the object and then call each one. I’m calling setProps(prop) within react, which is straightforward. I’m not sure how to do what you describe… although I’m guessing it’s possible.

Note, if you call app.modify({‘slider’: {‘value’: val}}) the slider callback with ‘value’ input will be called (as it should) assuming it exists. (I don’t know if that helps.)

Has anyone played more with Quart since 2019? Has there been any developments/considerations from the Plotly team, @chriddyp?

I would love to be able to use Dash with Quart, but I am not sure where to start :slight_smile:

4 Likes