Cache
Installation
pip install Flask-Caching
FileSystemCache
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from os import path
from flask_caching import Cache
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'FileSystemCache',
'CACHE_DIR': 'cache',
'CACHE_THRESHOLD': 200, # The maximum number of items the cache will store
'CACHE_DEFAULT_TIMEOUT': 60 # The default timeout, unit of time is seconds
})
cache.clear() # clean up cache files
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Click', id = 'button'),
], className='container')
# for the same parameter value, first call save results to cache
# second and later calls will reuse the results
# changing the parameter value, a new cache will be created
@cache.memoize()
def inside(value):
time.sleep(5)
return str(2*value)
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return inside(value) # pause 5 seconds
if __name__ == '__main__':
app.run_server(debug=True)
NullCache
Not cache
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask_caching import Cache
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'NullCache',
})
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Click', id = 'button'),
], className='container')
@cache.memoize()
def inside(value):
time.sleep(5)
return str(2*value)
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return inside(value) # pause 5 seconds
if __name__ == '__main__':
app.run_server(debug=True)
SimpleCache
Uses a python dictionary for caching, is not really thread safe
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask_caching import Cache
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'SimpleCache',
'CACHE_THRESHOLD': 200, # The maximum number of items the cache will store
'CACHE_DEFAULT_TIMEOUT': 30 # The default timeout, unit of time is seconds
})
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Click', id = 'button'),
], className='container')
@cache.memoize()
def inside(value):
time.sleep(5)
return str(2*value)
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return inside(value) # pause 5 seconds
if __name__ == '__main__':
app.run_server(debug=True)
RedisCache
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask_caching import Cache
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 60, # The default timeout, unit of time is seconds
'CACHE_REDIS_HOST': '127.0.0.1',
'CACHE_REDIS_PORT': 6379
})
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Click', id = 'button'),
], className='container')
# for the same parameter value, first call save results to cache
# second and later calls will reuse the results
# changing the parameter value, a new cache will be created
@cache.memoize()
def inside(value):
time.sleep(5)
return str(2*value)
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return inside(value) # pause 5 seconds
if __name__ == '__main__':
app.run_server(debug=True)
Cashed Function Calls Cashed Function
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from os import path
from flask_caching import Cache
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 60, # The default timeout, unit of time is seconds
'CACHE_REDIS_HOST': '127.0.0.1',
'CACHE_REDIS_PORT': 6379
})
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Click', id = 'button'),
html.Hr(),
dcc.Loading(html.H1(id = 'content_2')),
html.Button('Click 2', id = 'button2')
], className='container')
@cache.memoize()
def compute_expensive_data(value):
time.sleep(5)
return str(inside(value))
@cache.memoize()
def inside(value):
time.sleep(5)
return str(2*value)
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return inside(value) # pause 5 seconds
@app.callback(
Output('content_2', 'children'),
Input('button2', 'n_clicks'),
State('input', 'value')
)
def update_output_2(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return compute_expensive_data(value) # pause 10 seconds
if __name__ == '__main__':
app.run_server(debug=True)
Store Cache Data for Each Session on Server Side
Each tab creates a new session
import time
import uuid
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from os import path
from flask_caching import Cache
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 60, # The default timeout, unit of time is seconds
'CACHE_REDIS_HOST': '127.0.0.1',
'CACHE_REDIS_PORT': 6379
})
def get_layout():
uid = str(uuid.uuid4())
return html.Div([
dcc.Loading(html.H1(id = 'content')),
html.Button('Click', id = 'button'),
dcc.Store(data = uid, id='session-id')
], className='container')
# a new session is created each time the page is loaded
app.layout = get_layout # not get_layout()
@cache.memoize()
def compute_expensive_data(session_id):
time.sleep(5)
f = open('readme.txt', "r")
output_str = f.readline()
f.close()
return output_str + ' ' + str(session_id)
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('session-id', 'data')
)
def update_output_1(n_clicks, data):
if not n_clicks:
raise PreventUpdate
print(data) # display session id
return compute_expensive_data(data)
if __name__ == '__main__':
app.run_server(debug=True)
API
cached
- timeout, int
- key_prefix, str
- by default, key is flask_cache_view//_dash-update-component
- all function use flask_cache_view//_dash-update-component as the key
- one function with different parameter values use flask_cache_view//_dash-update-component as the key
@cache.cached(timeout=50, key_prefix='index_') # key is "flask_cache_index_"
def index(data, l):
time.sleep(5)
return data+'Cached for 50s'
unless, callable function
- cache will always execute the caching facilities unless this callable is true
- callable function takes function id, parameter values
- function id is the id of a function running on server side
def unless_function(*args, **kwargs):
print('args', args) # function index, parameter values
print('kwargs', kwargs) # parameters
if len(args[1]) > 4:
return True
return False
@cache.cached(timeout=50, unless = unless_function)
def index(data, l):
time.sleep(5)
return data+'Cached for 50s'
forced_update, callable function
- if this callable is true, cache value will be updated regardless cache is expired or not
- callable function takes parameter values
def force_function(*args, **kwargs):
print('args', args) # parameter values
print('kwargs', kwargs) # parameters
if len(args[0]) > 4:
return True
return False
@cache.cached(timeout=50, forced_update = force_function)
def index(data, l):
time.sleep(5)
return data+'Cached for 50s'
response_filter, callable function
- if this callable return true, cache the response content, otherwise, not cache the response content
- callable function takes the response content
def response_function(*args, **kwargs):
print('args', args) # content from index function
print('kwargs', kwargs)
return True
@cache.cached(timeout=50, response_filter = response_function)
def index(data, l):
time.sleep(5)
return data+'Cached for 50s'
query_string, boolean
- True, the cache key used will be the result of hashing the ordered query string parameters
- avoids creating different caches for the same query just because the parameters were passed in a different order
hash_method
- hashlib.sha1, hashlib.sha224, hashlib.sha256, hashlib.sha384, hashlib.sha512, hashlib.md5 (default)
make_cache_key, callable function
- generate a key with parameter values, flask_cache_ + (parameter values)
- callable function takes parameter values
def make_cache_key_function(*args, **kwargs):
print(args)
print(kwargs)
return str(args)+'_'+str(kwargs)
@cache.cached(timeout=50, make_cache_key = make_cache_key_function)
def index(data, l):
time.sleep(5)
return data+'Cached for 50s'
source_check, boolean
- if True, include the function’s source code in the hash
- exclusive to make_cache_key
memoize
- one function with different parameter values generates different keys
- two functions with the same parameter values generate different keys
- timeout, int
- make_name, callable function
- accepts the function name as the argument, returns a new string to be used as the function name
def make_function(*args, **kwargs):
print('args', args) # __main__.index
print('kwargs', kwargs)
return 'temp'
@cache.memoize(timeout=50, make_name = make_function)
def index(data, l):
time.sleep(5)
return data+'Cached for 50s'
unless, callable function
- cache will always execute the caching facilities unless this callable is true
- callable function takes function id, parameter values
forced_update, callable function
- if this callable is true, cache value will be updated regardless cache is expired or not
- callable function takes parameter values
response_filter, callable function
- if this callable return true, cache the response content, otherwise, not cache the response content
- callable function takes the response content
hash_method
- hashlib.sha1, hashlib.sha224, hashlib.sha256, hashlib.sha384, hashlib.sha512, hashlib.md5 (default)
source_check, boolean
- if True, include the function’s source code in the hash
args_to_ignore, boolean
- ignore parameter values while generating the cache key
@cache.memoize(timeout=50, args_to_ignore = ['update', 'data'])
def index(update, data, l):
time.sleep(5)
return str(update)+' '+data+'Cached for 50s'
Example
Button_1, get value by key, if key does not exist, create the key/value pair
Button_2, update value, if key does not exist, create the key/value pair
# redis-py
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import redis
import pickle
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
re = redis.Redis(host='localhost', port=6379, db=0)
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Button_1', id = 'button'),
dcc.Loading(html.H1(id = 'content2')),
html.Button('Button_2', id = 'button2'),
], className='container')
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
# get
if re.exists('container'):
return str(pickle.loads(re.get('container')))
# initialization
l = [False, value, {'k1':'v1', 'k2':'v2'}]
re.set('container', pickle.dumps(l), ex=50)
return str(l)
@app.callback(
Output('content2', 'children'),
Input('button2', 'n_clicks'),
State('input', 'value')
)
def update_output_2(n_clicks, value):
if not n_clicks:
raise PreventUpdate
# initialization
if not re.exists('container'):
temp = [False, value, {'k1':'v1', 'k2':'v2'}]
re.set('container', pickle.dumps(temp), ex=50)
return str(temp)
# update
temp = pickle.loads(re.get('container'))
temp[1] = value
re.set('container', pickle.dumps(temp), ex=50)
return str(temp)
if __name__ == '__main__':
app.run_server(debug=True)
# flask-caching
import time
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from flask_caching import Cache
from flask import request
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
cache = Cache(app.server, config={
'CACHE_TYPE': 'RedisCache',
'CACHE_DEFAULT_TIMEOUT': 60, # The default timeout, unit of time is seconds
'CACHE_REDIS_HOST': '127.0.0.1',
'CACHE_REDIS_PORT': 6379
})
app.layout = html.Div([
dcc.Input(id = 'input'),
html.Hr(),
dcc.Loading(html.H1(id = 'content')),
html.Button('Button_1', id = 'button'),
dcc.Loading(html.H1(id = 'content2')),
html.Button('Button_2', id = 'button2'),
], className='container')
def force_function(*args, **kwargs):
print('args', args) # parameter values
print('kwargs', kwargs) # parameters
return args[0]
# generate key by ignore parameter update
# the update value is used to decide whether force update or not
@cache.memoize(timeout=50, args_to_ignore = ['update'], forced_update = force_function)
def index(update, data, l):
time.sleep(5)
return str(update)+' '+data+'Cached for 50s'
@app.callback(
Output('content', 'children'),
Input('button', 'n_clicks'),
State('input', 'value')
)
def update_output_1(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return index(False, value, {'k1':'v1', 'k2':'v2'}) # get value, if key does not exist, create the key/value pair
@app.callback(
Output('content2', 'children'),
Input('button2', 'n_clicks'),
State('input', 'value')
)
def update_output_2(n_clicks, value):
if not n_clicks:
raise PreventUpdate
return index(True, value, {'k1':'v1', 'k2':'v2'}) # update value
if __name__ == '__main__':
app.run_server(debug=True)
Reference