Skip to content

Commit 1adb64c

Browse files
committed
Merge branch 'dev' into dash-3.0
2 parents 52b698a + 4c2a637 commit 1adb64c

File tree

8 files changed

+57
-8
lines changed

8 files changed

+57
-8
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
8989

9090
- [#3080](https://github.com/plotly/dash/pull/3080) Fix docstring generation for components using single-line or nonstandard-indent leading comments
9191
- [#3103](https://github.com/plotly/dash/pull/3103) Fix Graph component becomes unresponsive if an invalid figure is passed
92+
- [#3190](https://github.com/plotly/dash/pull/3190) Fix issue with cache key generation by adding option to include triggered inputs. Fixes [#3189](https://github.com/plotly/dash/issues/3189)
9293
- [#3130](https://github.com/plotly/dash/pull/3130) Fix HOST variable when using conda.
9394
- [#3066](https://github.com/plotly/dash/pull/3066) Improve performance of context components re-rendering.
9495

dash/_callback.py

+11
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def callback(
7474
cancel: Optional[Union[List[Input], Input]] = None,
7575
manager: Optional[BaseBackgroundCallbackManager] = None,
7676
cache_args_to_ignore: Optional[list] = None,
77+
cache_ignore_triggered=True,
7778
on_error: Optional[Callable[[Exception], Any]] = None,
7879
**_kwargs,
7980
):
@@ -143,6 +144,9 @@ def callback(
143144
with keyword arguments (Input/State provided in a dict),
144145
this should be a list of argument names as strings. Otherwise,
145146
this should be a list of argument indices as integers.
147+
:param cache_ignore_triggered:
148+
Whether to ignore which inputs triggered the callback when creating
149+
the cache.
146150
:param interval:
147151
Time to wait between the background callback update requests.
148152
:param on_error:
@@ -191,6 +195,8 @@ def callback(
191195
if cache_args_to_ignore:
192196
background_spec["cache_args_to_ignore"] = cache_args_to_ignore
193197

198+
background_spec["cache_ignore_triggered"] = cache_ignore_triggered
199+
194200
return register_callback(
195201
callback_list,
196202
callback_map,
@@ -404,11 +410,16 @@ def add_context(*args, **kwargs):
404410
job_id = flask.request.args.get("job")
405411
old_job = flask.request.args.getlist("oldJob")
406412

413+
cache_ignore_triggered = long.get("cache_ignore_triggered", True)
414+
407415
current_key = callback_manager.build_cache_key(
408416
func,
409417
# Inputs provided as dict is kwargs.
410418
func_args if func_args else func_kwargs,
411419
background.get("cache_args_to_ignore", []),
420+
None
421+
if cache_ignore_triggered
422+
else callback_ctx.get("triggered_inputs", []),
412423
)
413424

414425
if old_job:

dash/_jupyter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def run():
354354
self._servers[(host, port)] = server
355355

356356
# Wait for server to start up
357-
alive_url = f"http://{host}:{port}/_alive_{JupyterDash.alive_token}"
357+
alive_url = f"http://{host}:{port}{requests_pathname_prefix}_alive_{JupyterDash.alive_token}"
358358

359359
def _get_error():
360360
try:

dash/background_callback/managers/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def get_result(self, key, job):
5454
def get_updated_props(self, key):
5555
raise NotImplementedError
5656

57-
def build_cache_key(self, fn, args, cache_args_to_ignore):
57+
def build_cache_key(self, fn, args, cache_args_to_ignore, triggered):
5858
fn_source = inspect.getsource(fn)
5959

6060
if not isinstance(cache_args_to_ignore, (list, tuple)):
@@ -68,7 +68,7 @@ def build_cache_key(self, fn, args, cache_args_to_ignore):
6868
arg for i, arg in enumerate(args) if i not in cache_args_to_ignore
6969
]
7070

71-
hash_dict = dict(args=args, fn_source=fn_source)
71+
hash_dict = dict(args=args, fn_source=fn_source, triggered=triggered)
7272

7373
if self.cache_by is not None:
7474
# Caching enabled

dash/background_callback/managers/celery_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def __init__(self, celery_app, cache_by=None, expire=None):
2626
:param cache_by:
2727
A list of zero-argument functions. When provided, caching is enabled and
2828
the return values of these functions are combined with the callback
29-
function's input arguments and source code to generate cache keys.
29+
function's input arguments, triggered inputs and source code to generate cache keys.
3030
:param expire:
3131
If provided, a cache entry will be removed when it has not been accessed
3232
for ``expire`` seconds. If not provided, the lifetime of cache entries

dash/background_callback/managers/diskcache_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(self, cache=None, cache_by=None, expire=None):
2525
:param cache_by:
2626
A list of zero-argument functions. When provided, caching is enabled and
2727
the return values of these functions are combined with the callback
28-
function's input arguments and source code to generate cache keys.
28+
function's input arguments, triggered inputs and source code to generate cache keys.
2929
:param expire:
3030
If provided, a cache entry will be removed when it has not been accessed
3131
for ``expire`` seconds. If not provided, the lifetime of cache entries

dash/dash-renderer/src/actions/callbacks.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -664,9 +664,14 @@ function getTriggeredId(triggered: string[]): string | object | undefined {
664664
// for regular callbacks, takes the first triggered prop_id, e.g. "btn.n_clicks" and returns "btn"
665665
// for pattern matching callback, e.g. '{"index":0, "type":"btn"}' and returns {index:0, type: "btn"}'
666666
if (triggered && triggered.length) {
667-
let componentId = triggered[0].split('.')[0];
668-
if (componentId.startsWith('{')) {
669-
componentId = JSON.parse(componentId);
667+
const trig = triggered[0];
668+
let componentId;
669+
if (trig.startsWith('{')) {
670+
componentId = JSON.parse(
671+
trig.substring(0, trig.lastIndexOf('}') + 1)
672+
);
673+
} else {
674+
componentId = trig.split('.')[0];
670675
}
671676
return componentId;
672677
}

tests/integration/clientside/test_clientside.py

+32
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from dash import Dash, Input, Output, State, ClientsideFunction, ALL, html, dcc
55
from selenium.webdriver.common.keys import Keys
66

7+
from dash.dependencies import MATCH
8+
79

810
def test_clsd001_simple_clientside_serverside_callback(dash_duo):
911
app = Dash(__name__, assets_folder="assets")
@@ -905,3 +907,33 @@ def update_output(value):
905907
dash_duo.find_element("#input").send_keys("hello world")
906908
dash_duo.wait_for_text_to_equal("#output-serverside", 'Server says "hello world"')
907909
dash_duo.wait_for_text_to_equal("#output-clientside", 'Client says "hello world"')
910+
911+
912+
def test_clsd022_clientside_pattern_matching_dots(dash_duo):
913+
# Test for bug https://github.com/plotly/dash/issues/3163
914+
# Allow dict id to contains a dot in the dict when using clientside callback.
915+
app = Dash()
916+
app.layout = html.Div(
917+
[
918+
html.Button("click", id={"type": "input", "index": 3.1}),
919+
html.Div(id={"type": "output", "index": 3.1}, className="output"),
920+
]
921+
)
922+
923+
app.clientside_callback(
924+
"""
925+
function(n_clicks) {
926+
return `clicked ${n_clicks}`;
927+
}
928+
""",
929+
Output({"type": "output", "index": MATCH}, "children"),
930+
Input({"type": "input", "index": MATCH}, "n_clicks"),
931+
prevent_initial_call=True,
932+
)
933+
934+
dash_duo.start_server(app)
935+
936+
dash_duo.find_element("button").click()
937+
dash_duo.wait_for_text_to_equal(".output", "clicked 1")
938+
939+
assert dash_duo.get_logs() == []

0 commit comments

Comments
 (0)