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).
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.
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)
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.
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.
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.
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)…
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.)