Hm, good point. This isn’t possible right now, but it’s a good idea. One way that this could be implemented is through a custom Dash component with a parameter that listens to the beforeUnload
JS event and fires an update to Dash. In fact, it looks like there is already a React component written in the community that does this: react-beforeunload - npm. Here is the tutorial on creating custom Dash components in React: Build Your Own Components | Dash for Python Documentation | Plotly
Very nice solution! A couple of notes on this:
Note that this might not work in a multi-process app. If there are multiple people using the app, then you’ll want to run the app with multiple workers (e.g. with gunicorn
with $ gunicorn app:server --workers 4 --threads 2
) so that multiple requests can be handled in parallel. The dict that you create and mutate during the lifetime of the app won’t be shared across workers: one request could cache the result on one worker and the next request could end up using a different worker (without the cached value!). For more on this, see Global variables sharing with mutex - #4 by nedned
However, you could use a similar pattern but write the data to the disk (you’ll still have to serialize and unserialize it). Also, you could limit the cache store to be roughly the size of the number of concurrent users - so, if you expect 10 users, the 11th entry in your cache store would get dropped - and fall back on the original, uncached solution if it’s not in your session store.
Here is an example that writes the data to the disk and only holds N items in the session store:
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import datetime
from flask_caching import Cache
import pandas as pd
import time
import uuid
app = dash.Dash()
cache = Cache(app.server, config={
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory',
'CACHE_THRESHOLD': 50 # should be equal to maximum number of active users
})
@cache.memoize()
def query_data(session_id):
df = pd.DataFrame({'a': [datetime.datetime.now()]})
return df.to_json()
def dataframe(session_id):
return pd.read_json(query_data(session_id))
def serve_layout():
session_id = str(uuid.uuid4())
df = dataframe(session_id)
return html.Div([
html.Div(session_id, id='session-id', style={'display': 'none'}),
dcc.Dropdown(id='dropdown', value='a', options=[
{'label': i, 'value': i} for i in ['a', 'b', 'c', 'd']
]),
html.Pre(id='output')
])
app.layout = serve_layout
@app.callback(Output('output', 'children'),
[Input('dropdown', 'value'),
Input('session-id', 'children')])
def display_value(value, session_id):
df = dataframe(session_id)
return df.to_csv()
if __name__ == '__main__':
app.run_server(debug=True)