From 1880fc739e0e109e5fe7e5bf9f402148fdf3496c Mon Sep 17 00:00:00 2001 From: philippe Date: Mon, 4 Jul 2022 14:34:22 -0400 Subject: [PATCH 1/6] Rename long callbacks to background. --- dash/__init__.py | 5 ++ dash/_callback.py | 76 ++++++++++--------- dash/dash.py | 34 +++++---- dash/long_callback/__init__.py | 11 +++ dash/long_callback/managers/celery_manager.py | 2 + .../managers/diskcache_manager.py | 2 + .../long_callback/app_callback_ctx.py | 4 +- .../long_callback/app_pattern_matching.py | 2 +- .../long_callback/app_short_interval.py | 10 +-- .../long_callback/app_side_update.py | 10 +-- 10 files changed, 92 insertions(+), 64 deletions(-) diff --git a/dash/__init__.py b/dash/__init__.py index dc22529454..9120954c3e 100644 --- a/dash/__init__.py +++ b/dash/__init__.py @@ -26,6 +26,10 @@ get_relative_path, strip_relative_path, ) +from .long_callback import ( # noqa: F401,E402 + CeleryBackgroundExecutor, + DiskcacheBackgroundExecutor, +) from ._pages import register_page, PAGE_REGISTRY as page_registry # noqa: F401,E402 @@ -35,4 +39,5 @@ page_container, ) + ctx = callback_context diff --git a/dash/_callback.py b/dash/_callback.py index e7763b4702..6f48ca63c1 100644 --- a/dash/_callback.py +++ b/dash/_callback.py @@ -53,14 +53,14 @@ def is_no_update(obj): # pylint: disable=too-many-locals def callback( *_args, - long=False, - long_interval=1000, - long_progress=None, - long_progress_default=None, - long_running=None, - long_cancel=None, - long_manager=None, - long_cache_args_to_ignore=None, + background=False, + interval=1000, + progress=None, + progress_default=None, + running=None, + cancel=None, + manager=None, + cache_args_to_ignore=None, **_kwargs, ): """ @@ -81,35 +81,35 @@ def callback( `False` and unlike `app.callback` is not configurable at the app level. :Keyword Arguments: - :param long: + :param background: Mark the callback as a long callback to execute in a manager for callbacks that take a long time without locking up the Dash app or timing out. - :param long_manager: - A long callback manager instance. Currently an instance of one of - `DiskcacheLongCallbackManager` or `CeleryLongCallbackManager`. - Defaults to the `long_callback_manager` instance provided to the + :param manager: + A long callback manager instance. Currently, an instance of one of + `DiskcacheManager` or `CeleryManager`. + Defaults to the `background_callback_manager` instance provided to the `dash.Dash constructor`. - - A diskcache manager (`DiskcacheLongCallbackManager`) that runs callback + - A diskcache manager (`DiskcacheManager`) that runs callback logic in a separate process and stores the results to disk using the diskcache library. This is the easiest backend to use for local development. - - A Celery manager (`CeleryLongCallbackManager`) that runs callback logic + - A Celery manager (`CeleryManager`) that runs callback logic in a celery worker and returns results to the Dash app through a Celery broker like RabbitMQ or Redis. - :param long_running: + :param running: A list of 3-element tuples. The first element of each tuple should be an `Output` dependency object referencing a property of a component in the app layout. The second element is the value that the property should be set to while the callback is running, and the third element is the value the property should be set to when the callback completes. - :param long_cancel: + :param cancel: A list of `Input` dependency objects that reference a property of a component in the app's layout. When the value of this property changes while a callback is running, the callback is canceled. Note that the value of the property is not significant, any change in value will result in the cancellation of the running job (if any). - :param long_progress: + :param progress: An `Output` dependency grouping that references properties of components in the app's layout. When provided, the decorated function will be called with an extra argument as the first argument to the @@ -118,18 +118,18 @@ def callback( current progress. This function accepts a single argument, which correspond to the grouping of properties specified in the provided `Output` dependency grouping - :param long_progress_default: + :param progress_default: A grouping of values that should be assigned to the components specified by the `progress` argument when the callback is not in progress. If `progress_default` is not provided, all the dependency properties specified in `progress` will be set to `None` when the callback is not running. - :param long_cache_args_to_ignore: + :param cache_args_to_ignore: Arguments to ignore when caching is enabled. If callback is configured with keyword arguments (Input/State provided in a dict), this should be a list of argument names as strings. Otherwise, this should be a list of argument indices as integers. - :param long_interval: + :param interval: Time to wait between the long callback update requests. """ @@ -141,32 +141,32 @@ def callback( callback_map = _kwargs.pop("callback_map", GLOBAL_CALLBACK_MAP) callback_list = _kwargs.pop("callback_list", GLOBAL_CALLBACK_LIST) - if long: + if background: long_spec = { - "interval": long_interval, + "interval": interval, } - if long_manager: - long_spec["manager"] = long_manager + if manager: + long_spec["manager"] = manager - if long_progress: - long_spec["progress"] = coerce_to_list(long_progress) + if progress: + long_spec["progress"] = coerce_to_list(progress) validate_long_inputs(long_spec["progress"]) - if long_progress_default: - long_spec["progressDefault"] = coerce_to_list(long_progress_default) + if progress_default: + long_spec["progressDefault"] = coerce_to_list(progress_default) if not len(long_spec["progress"]) == len(long_spec["progressDefault"]): raise Exception( "Progress and progress default needs to be of same length" ) - if long_running: - long_spec["running"] = coerce_to_list(long_running) + if running: + long_spec["running"] = coerce_to_list(running) validate_long_inputs(x[0] for x in long_spec["running"]) - if long_cancel: - cancel_inputs = coerce_to_list(long_cancel) + if cancel: + cancel_inputs = coerce_to_list(cancel) validate_long_inputs(cancel_inputs) cancels_output = [Output(c.component_id, "id") for c in cancel_inputs] @@ -176,10 +176,12 @@ def callback( @callback(cancels_output, cancel_inputs, prevent_initial_call=True) def cancel_call(*_): job_ids = flask.request.args.getlist("cancelJob") - manager = long_manager or context_value.get().long_callback_manager + executor = ( + manager or context_value.get().background_callback_manager + ) if job_ids: for job_id in job_ids: - manager.terminate_job(job_id) + executor.terminate_job(job_id) return NoUpdate() except DuplicateCallback: @@ -187,8 +189,8 @@ def cancel_call(*_): long_spec["cancel"] = [c.to_dict() for c in cancel_inputs] - if long_cache_args_to_ignore: - long_spec["cache_args_to_ignore"] = long_cache_args_to_ignore + if cache_args_to_ignore: + long_spec["cache_args_to_ignore"] = cache_args_to_ignore return register_callback( callback_list, diff --git a/dash/dash.py b/dash/dash.py index 7786f73667..782c38611d 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -322,9 +322,12 @@ class Dash: want to control the document.title through a separate component or clientside callback. - :param long_callback_manager: Long callback manager instance to support the - ``@app.long_callback`` decorator. Currently an instance of one of - ``DiskcacheLongCallbackManager`` or ``CeleryLongCallbackManager`` + :param long_callback_manager: Deprecated, use ``background_callback_manager`` + instead. + + :param background_callback_manager: Background callback manager instance + to support the ``@callback(..., background=True)`` decorator. + One of ``DiskcacheManager`` or ``CeleryManager`` currently supported. """ def __init__( # pylint: disable=too-many-statements @@ -356,6 +359,7 @@ def __init__( # pylint: disable=too-many-statements title="Dash", update_title="Updating...", long_callback_manager=None, + background_callback_manager=None, **obsolete, ): _validate.check_obsolete(obsolete) @@ -484,7 +488,7 @@ def __init__( # pylint: disable=too-many-statements self._assets_files = [] self._long_callback_count = 0 - self._long_callback_manager = long_callback_manager + self._background_manager = background_callback_manager or long_callback_manager self.logger = logging.getLogger(name) self.logger.addHandler(logging.StreamHandler(stream=sys.stdout)) @@ -1153,14 +1157,14 @@ def long_callback( """ return _callback.callback( *_args, - long=True, - long_manager=manager, - long_interval=interval, - long_progress=progress, - long_progress_default=progress_default, - long_running=running, - long_cancel=cancel, - long_cache_args_to_ignore=cache_args_to_ignore, + background=True, + manager=manager, + interval=interval, + progress=progress, + progress_default=progress_default, + running=running, + cancel=cancel, + cache_args_to_ignore=cache_args_to_ignore, callback_map=self.callback_map, callback_list=self._callback_list, config_prevent_initial_callbacks=self.config.prevent_initial_callbacks, @@ -1186,7 +1190,9 @@ def dispatch(self): input_values ) = inputs_to_dict(inputs) g.state_values = inputs_to_dict(state) # pylint: disable=assigning-non-slot - g.long_callback_manager = self._long_callback_manager # pylint: disable=E0237 + g.background_callback_manager = ( + self._background_manager + ) # pylint: disable=E0237 changed_props = body.get("changedPropIds", []) g.triggered_inputs = [ # pylint: disable=assigning-non-slot {"prop_id": x, "value": input_values.get(x)} for x in changed_props @@ -1255,7 +1261,7 @@ def dispatch(self): func, *args, outputs_list=outputs_list, - long_callback_manager=self._long_callback_manager, + long_callback_manager=self._background_manager, callback_context=g, ) ) diff --git a/dash/long_callback/__init__.py b/dash/long_callback/__init__.py index 54af16e92c..76dffbd43a 100644 --- a/dash/long_callback/__init__.py +++ b/dash/long_callback/__init__.py @@ -1,2 +1,13 @@ from .managers.celery_manager import CeleryLongCallbackManager # noqa: F401,E402 from .managers.diskcache_manager import DiskcacheLongCallbackManager # noqa: F401,E402 + + +# Renamed without `LongCallback` + + +class DiskcacheBackgroundExecutor(DiskcacheLongCallbackManager): + """Manage the background execution of callbacks with subprocesses and a diskcache result backend.""" + + +class CeleryBackgroundExecutor(CeleryLongCallbackManager): + """Manage background execution of callbacks with a celery queue.""" diff --git a/dash/long_callback/managers/celery_manager.py b/dash/long_callback/managers/celery_manager.py index ae6f14903e..a4449c0fab 100644 --- a/dash/long_callback/managers/celery_manager.py +++ b/dash/long_callback/managers/celery_manager.py @@ -13,6 +13,8 @@ class CeleryLongCallbackManager(BaseLongCallbackManager): + """Deprecated: use `import CeleryManager from dash` instead.""" + def __init__(self, celery_app, cache_by=None, expire=None): """ Long callback manager that runs callback logic on a celery task queue, diff --git a/dash/long_callback/managers/diskcache_manager.py b/dash/long_callback/managers/diskcache_manager.py index 44979c7d09..9fb527df52 100644 --- a/dash/long_callback/managers/diskcache_manager.py +++ b/dash/long_callback/managers/diskcache_manager.py @@ -7,6 +7,8 @@ class DiskcacheLongCallbackManager(BaseLongCallbackManager): + """Deprecated: use `import DiskcacheManager from dash` instead.""" + def __init__(self, cache=None, cache_by=None, expire=None): """ Long callback manager that runs callback logic in a subprocess and stores diff --git a/tests/integration/long_callback/app_callback_ctx.py b/tests/integration/long_callback/app_callback_ctx.py index 0bb2ff1edd..1f69079308 100644 --- a/tests/integration/long_callback/app_callback_ctx.py +++ b/tests/integration/long_callback/app_callback_ctx.py @@ -23,9 +23,9 @@ @callback( Output("result", "children"), [Input({"type": "run-button", "index": ALL}, "n_clicks")], - long=True, + background=True, prevent_initial_call=True, - long_running=[(Output("running", "children"), "on", "off")], + running=[(Output("running", "children"), "on", "off")], ) def update_output(n_clicks): triggered = json.loads(ctx.triggered[0]["prop_id"].split(".")[0]) diff --git a/tests/integration/long_callback/app_pattern_matching.py b/tests/integration/long_callback/app_pattern_matching.py index aa176dda32..4cfc2ce1a9 100644 --- a/tests/integration/long_callback/app_pattern_matching.py +++ b/tests/integration/long_callback/app_pattern_matching.py @@ -20,7 +20,7 @@ @callback( Output("result", "children"), [Input({"type": "run-button", "index": ALL}, "n_clicks")], - long=True, + background=True, prevent_initial_call=True, ) def update_output(n_clicks): diff --git a/tests/integration/long_callback/app_short_interval.py b/tests/integration/long_callback/app_short_interval.py index 249a65e5a6..f8d2e1bbe0 100644 --- a/tests/integration/long_callback/app_short_interval.py +++ b/tests/integration/long_callback/app_short_interval.py @@ -21,11 +21,11 @@ @callback( Output("result", "children"), [Input("run-button", "n_clicks")], - long=True, - long_progress=Output("status", "children"), - long_progress_default="Finished", - long_cancel=[Input("cancel-button", "n_clicks")], - long_interval=0, + background=True, + progress=Output("status", "children"), + progress_default="Finished", + cancel=[Input("cancel-button", "n_clicks")], + interval=0, prevent_initial_call=True, ) def update_output(set_progress, n_clicks): diff --git a/tests/integration/long_callback/app_side_update.py b/tests/integration/long_callback/app_side_update.py index 6d0cf2cd0e..41db33d489 100644 --- a/tests/integration/long_callback/app_side_update.py +++ b/tests/integration/long_callback/app_side_update.py @@ -22,11 +22,11 @@ @callback( Output("result", "children"), [Input("run-button", "n_clicks")], - long=True, - long_progress=Output("status", "children"), - long_progress_default="Finished", - long_cancel=[Input("cancel-button", "n_clicks")], - long_interval=0, + background=True, + progress=Output("status", "children"), + progress_default="Finished", + cancel=[Input("cancel-button", "n_clicks")], + interval=0, prevent_initial_call=True, ) def update_output(set_progress, n_clicks): From 851c871d79a2274a9bd73583bc8651ddb191eff1 Mon Sep 17 00:00:00 2001 From: philippe Date: Mon, 4 Jul 2022 15:13:46 -0400 Subject: [PATCH 2/6] Update changelog. --- CHANGELOG.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5048a7d6f..1534d3a02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,15 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added - [#2039](https://github.com/plotly/dash/pull/2039) Long callback changes: - - Add `long=False` to `dash.callback` to use instead of `app.long_callback`. - - Add previous `app.long_callback` arguments to `dash.callback` prefixed with `long_` (`interval`, `running`, `cancel`, `progress`, `progress_default`, `cache_args_to_ignore`, `manager`) + - Add `background=False` to `dash.callback` to use instead of `app.long_callback`. + - Add previous `app.long_callback` arguments to `dash.callback` (`interval`, `running`, `cancel`, `progress`, `progress_default`, `cache_args_to_ignore`, `manager`) + +## Changed + +- [#2116](https://github.com/plotly/dash/pull/2116) Rename long callbacks to background callbacks + - Deprecated `dash.long_callback.managers.CeleryLongCallbackManager`, use `dash.CeleryManager` instead. + - Deprecated `dash.long_callback.managers.DiskcacheLongCallbackManager`, use `dash.DiskcacheManager` instead. + - Deprecated dash constructor argument `long_callback_manager` in favor of `background_callback_manager`. ## [2.5.1] - 2022-06-13 From 6517e8a3016f90505c7bfd489a363db97341f9ca Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 5 Jul 2022 14:19:51 -0400 Subject: [PATCH 3/6] Switch deprecated managers class. --- dash/__init__.py | 4 ++-- dash/long_callback/__init__.py | 21 +++++++------------ dash/long_callback/managers/celery_manager.py | 8 +++++-- .../managers/diskcache_manager.py | 8 +++++-- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/dash/__init__.py b/dash/__init__.py index 9120954c3e..6a755c7a4e 100644 --- a/dash/__init__.py +++ b/dash/__init__.py @@ -27,8 +27,8 @@ strip_relative_path, ) from .long_callback import ( # noqa: F401,E402 - CeleryBackgroundExecutor, - DiskcacheBackgroundExecutor, + CeleryManager, + DiskcacheManager, ) diff --git a/dash/long_callback/__init__.py b/dash/long_callback/__init__.py index 76dffbd43a..d89324f749 100644 --- a/dash/long_callback/__init__.py +++ b/dash/long_callback/__init__.py @@ -1,13 +1,8 @@ -from .managers.celery_manager import CeleryLongCallbackManager # noqa: F401,E402 -from .managers.diskcache_manager import DiskcacheLongCallbackManager # noqa: F401,E402 - - -# Renamed without `LongCallback` - - -class DiskcacheBackgroundExecutor(DiskcacheLongCallbackManager): - """Manage the background execution of callbacks with subprocesses and a diskcache result backend.""" - - -class CeleryBackgroundExecutor(CeleryLongCallbackManager): - """Manage background execution of callbacks with a celery queue.""" +from .managers.celery_manager import ( # noqa: F401,E402 + CeleryLongCallbackManager, + CeleryManager, +) +from .managers.diskcache_manager import ( # noqa: F401,E402 + DiskcacheLongCallbackManager, + DiskcacheManager, +) diff --git a/dash/long_callback/managers/celery_manager.py b/dash/long_callback/managers/celery_manager.py index a4449c0fab..cc69a2bc27 100644 --- a/dash/long_callback/managers/celery_manager.py +++ b/dash/long_callback/managers/celery_manager.py @@ -12,8 +12,8 @@ from dash.long_callback.managers import BaseLongCallbackManager -class CeleryLongCallbackManager(BaseLongCallbackManager): - """Deprecated: use `import CeleryManager from dash` instead.""" +class CeleryManager(BaseLongCallbackManager): + """Manage background execution of callbacks with a celery queue.""" def __init__(self, celery_app, cache_by=None, expire=None): """ @@ -183,3 +183,7 @@ def run(): ctx.run(run) return job_fn + + +class CeleryLongCallbackManager(CeleryManager): + """Deprecated: use `from dash import CeleryManager` instead.""" diff --git a/dash/long_callback/managers/diskcache_manager.py b/dash/long_callback/managers/diskcache_manager.py index 9fb527df52..7ab3d15061 100644 --- a/dash/long_callback/managers/diskcache_manager.py +++ b/dash/long_callback/managers/diskcache_manager.py @@ -6,8 +6,8 @@ _pending_value = "__$pending__" -class DiskcacheLongCallbackManager(BaseLongCallbackManager): - """Deprecated: use `import DiskcacheManager from dash` instead.""" +class DiskcacheManager(BaseLongCallbackManager): + """Manage the background execution of callbacks with subprocesses and a diskcache result backend.""" def __init__(self, cache=None, cache_by=None, expire=None): """ @@ -184,3 +184,7 @@ def _set_progress(progress_value): cache.set(result_key, user_callback_output) return job_fn + + +class DiskcacheLongCallbackManager(DiskcacheManager): + """Deprecated: use `from dash import DiskcacheManager` instead.""" From 528ee0908cdd6cb701f3e4d2713f375b5c393f00 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 5 Jul 2022 15:33:33 -0400 Subject: [PATCH 4/6] Add test locks to long callback tests. --- tests/integration/long_callback/app_callback_ctx.py | 6 ++++-- .../long_callback/app_pattern_matching.py | 6 ++++-- .../integration/long_callback/app_short_interval.py | 4 +++- tests/integration/long_callback/app_side_update.py | 4 +++- .../long_callback/test_basic_long_callback.py | 12 ++++++++---- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/integration/long_callback/app_callback_ctx.py b/tests/integration/long_callback/app_callback_ctx.py index 1f69079308..0ebdac3029 100644 --- a/tests/integration/long_callback/app_callback_ctx.py +++ b/tests/integration/long_callback/app_callback_ctx.py @@ -18,6 +18,7 @@ html.Div(id="running"), ] ) +app.test_lock = lock = long_callback_manager.test_lock @callback( @@ -28,8 +29,9 @@ running=[(Output("running", "children"), "on", "off")], ) def update_output(n_clicks): - triggered = json.loads(ctx.triggered[0]["prop_id"].split(".")[0]) - return json.dumps(dict(triggered=triggered, value=n_clicks[triggered["index"]])) + with lock: + triggered = json.loads(ctx.triggered[0]["prop_id"].split(".")[0]) + return json.dumps(dict(triggered=triggered, value=n_clicks[triggered["index"]])) if __name__ == "__main__": diff --git a/tests/integration/long_callback/app_pattern_matching.py b/tests/integration/long_callback/app_pattern_matching.py index 4cfc2ce1a9..b9ec293540 100644 --- a/tests/integration/long_callback/app_pattern_matching.py +++ b/tests/integration/long_callback/app_pattern_matching.py @@ -15,6 +15,7 @@ html.Div(id="result", children="No results"), ] ) +app.test_lock = lock = long_callback_manager.test_lock @callback( @@ -24,8 +25,9 @@ prevent_initial_call=True, ) def update_output(n_clicks): - found = max(x for x in n_clicks if x is not None) - return f"Clicked '{found}'" + with lock: + found = max(x for x in n_clicks if x is not None) + return f"Clicked '{found}'" if __name__ == "__main__": diff --git a/tests/integration/long_callback/app_short_interval.py b/tests/integration/long_callback/app_short_interval.py index f8d2e1bbe0..2c44dc35ef 100644 --- a/tests/integration/long_callback/app_short_interval.py +++ b/tests/integration/long_callback/app_short_interval.py @@ -16,6 +16,7 @@ html.Div(id="result", children="No results"), ] ) +app.test_lock = lock = long_callback_manager.test_lock @callback( @@ -30,7 +31,8 @@ ) def update_output(set_progress, n_clicks): for i in range(4): - set_progress(f"Progress {i}/4") + with lock: + set_progress(f"Progress {i}/4") time.sleep(1) return f"Clicked '{n_clicks}'" diff --git a/tests/integration/long_callback/app_side_update.py b/tests/integration/long_callback/app_side_update.py index 41db33d489..5532d896d1 100644 --- a/tests/integration/long_callback/app_side_update.py +++ b/tests/integration/long_callback/app_side_update.py @@ -17,6 +17,7 @@ html.Div(id="side-status"), ] ) +app.test_lock = lock = long_callback_manager.test_lock @callback( @@ -32,7 +33,8 @@ def update_output(set_progress, n_clicks): print("trigger") for i in range(4): - set_progress(f"Progress {i}/4") + with lock: + set_progress(f"Progress {i}/4") time.sleep(1) return f"Clicked '{n_clicks}'" diff --git a/tests/integration/long_callback/test_basic_long_callback.py b/tests/integration/long_callback/test_basic_long_callback.py index 70cc821019..b98a67afd6 100644 --- a/tests/integration/long_callback/test_basic_long_callback.py +++ b/tests/integration/long_callback/test_basic_long_callback.py @@ -487,7 +487,8 @@ def make_expect(n): def test_lcbc009_short_interval(dash_duo, manager): with setup_long_callback_app(manager, "app_short_interval") as app: dash_duo.start_server(app) - dash_duo.find_element("#run-button").click() + with app.test_lock: + dash_duo.find_element("#run-button").click() dash_duo.wait_for_text_to_equal("#status", "Progress 2/4", 20) dash_duo.wait_for_text_to_equal("#status", "Finished", 12) dash_duo.wait_for_text_to_equal("#result", "Clicked '1'") @@ -500,7 +501,8 @@ def test_lcbc009_short_interval(dash_duo, manager): def test_lcbc010_side_updates(dash_duo, manager): with setup_long_callback_app(manager, "app_side_update") as app: dash_duo.start_server(app) - dash_duo.find_element("#run-button").click() + with app.test_lock: + dash_duo.find_element("#run-button").click() for i in range(1, 4): dash_duo.wait_for_text_to_equal("#side-status", f"Side Progress {i}/4") @@ -510,7 +512,8 @@ def test_lcbc011_long_pattern_matching(dash_duo, manager): dash_duo.start_server(app) for i in range(1, 4): for _ in range(i): - dash_duo.find_element(f"button:nth-child({i})").click() + with app.test_lock: + dash_duo.find_element(f"button:nth-child({i})").click() dash_duo.wait_for_text_to_equal("#result", f"Clicked '{i}'") @@ -518,7 +521,8 @@ def test_lcbc011_long_pattern_matching(dash_duo, manager): def test_lcbc012_long_callback_ctx(dash_duo, manager): with setup_long_callback_app(manager, "app_callback_ctx") as app: dash_duo.start_server(app) - dash_duo.find_element("button:nth-child(1)").click() + with app.test_lock: + dash_duo.find_element("button:nth-child(1)").click() dash_duo.wait_for_text_to_equal("#running", "off") output = json.loads(dash_duo.find_element("#result").text) From 4799a5ad3645e17afc96dc70d39bd5791dfd8b71 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 6 Jul 2022 15:07:55 -0400 Subject: [PATCH 5/6] Remove test locks. --- .../long_callback/app_callback_ctx.py | 5 ++--- tests/integration/long_callback/app_error.py | 22 +++++++++---------- .../long_callback/app_pattern_matching.py | 5 ++--- .../long_callback/app_short_interval.py | 3 +-- .../long_callback/app_side_update.py | 4 +--- .../long_callback/test_basic_long_callback.py | 12 ++++------ 6 files changed, 20 insertions(+), 31 deletions(-) diff --git a/tests/integration/long_callback/app_callback_ctx.py b/tests/integration/long_callback/app_callback_ctx.py index 0ebdac3029..f3d95346eb 100644 --- a/tests/integration/long_callback/app_callback_ctx.py +++ b/tests/integration/long_callback/app_callback_ctx.py @@ -29,9 +29,8 @@ running=[(Output("running", "children"), "on", "off")], ) def update_output(n_clicks): - with lock: - triggered = json.loads(ctx.triggered[0]["prop_id"].split(".")[0]) - return json.dumps(dict(triggered=triggered, value=n_clicks[triggered["index"]])) + triggered = json.loads(ctx.triggered[0]["prop_id"].split(".")[0]) + return json.dumps(dict(triggered=triggered, value=n_clicks[triggered["index"]])) if __name__ == "__main__": diff --git a/tests/integration/long_callback/app_error.py b/tests/integration/long_callback/app_error.py index c0e47abc3e..23c3bc4b6e 100644 --- a/tests/integration/long_callback/app_error.py +++ b/tests/integration/long_callback/app_error.py @@ -39,13 +39,12 @@ def callback(n_clicks): if os.getenv("LONG_CALLBACK_MANAGER") != "celery": # Diskmanager needs some time, celery takes too long. time.sleep(1) - with lock: - if n_clicks == 2: - raise Exception("bad error") + if n_clicks == 2: + raise Exception("bad error") - if n_clicks == 4: - raise PreventUpdate - return f"Clicked {n_clicks} times" + if n_clicks == 4: + raise PreventUpdate + return f"Clicked {n_clicks} times" @app.long_callback( @@ -58,12 +57,11 @@ def callback(n_clicks): prevent_initial_call=True, ) def long_multi(n_clicks): - with lock: - return ( - [f"Updated: {n_clicks}"] - + [i for i in range(1, n_clicks + 1)] - + [no_update for _ in range(n_clicks + 1, 4)] - ) + return ( + [f"Updated: {n_clicks}"] + + [i for i in range(1, n_clicks + 1)] + + [no_update for _ in range(n_clicks + 1, 4)] + ) if __name__ == "__main__": diff --git a/tests/integration/long_callback/app_pattern_matching.py b/tests/integration/long_callback/app_pattern_matching.py index b9ec293540..65aa906bcd 100644 --- a/tests/integration/long_callback/app_pattern_matching.py +++ b/tests/integration/long_callback/app_pattern_matching.py @@ -25,9 +25,8 @@ prevent_initial_call=True, ) def update_output(n_clicks): - with lock: - found = max(x for x in n_clicks if x is not None) - return f"Clicked '{found}'" + found = max(x for x in n_clicks if x is not None) + return f"Clicked '{found}'" if __name__ == "__main__": diff --git a/tests/integration/long_callback/app_short_interval.py b/tests/integration/long_callback/app_short_interval.py index 2c44dc35ef..2dd4c3f5fc 100644 --- a/tests/integration/long_callback/app_short_interval.py +++ b/tests/integration/long_callback/app_short_interval.py @@ -31,8 +31,7 @@ ) def update_output(set_progress, n_clicks): for i in range(4): - with lock: - set_progress(f"Progress {i}/4") + set_progress(f"Progress {i}/4") time.sleep(1) return f"Clicked '{n_clicks}'" diff --git a/tests/integration/long_callback/app_side_update.py b/tests/integration/long_callback/app_side_update.py index 5532d896d1..e54a3318aa 100644 --- a/tests/integration/long_callback/app_side_update.py +++ b/tests/integration/long_callback/app_side_update.py @@ -31,10 +31,8 @@ prevent_initial_call=True, ) def update_output(set_progress, n_clicks): - print("trigger") for i in range(4): - with lock: - set_progress(f"Progress {i}/4") + set_progress(f"Progress {i}/4") time.sleep(1) return f"Clicked '{n_clicks}'" diff --git a/tests/integration/long_callback/test_basic_long_callback.py b/tests/integration/long_callback/test_basic_long_callback.py index b98a67afd6..70cc821019 100644 --- a/tests/integration/long_callback/test_basic_long_callback.py +++ b/tests/integration/long_callback/test_basic_long_callback.py @@ -487,8 +487,7 @@ def make_expect(n): def test_lcbc009_short_interval(dash_duo, manager): with setup_long_callback_app(manager, "app_short_interval") as app: dash_duo.start_server(app) - with app.test_lock: - dash_duo.find_element("#run-button").click() + dash_duo.find_element("#run-button").click() dash_duo.wait_for_text_to_equal("#status", "Progress 2/4", 20) dash_duo.wait_for_text_to_equal("#status", "Finished", 12) dash_duo.wait_for_text_to_equal("#result", "Clicked '1'") @@ -501,8 +500,7 @@ def test_lcbc009_short_interval(dash_duo, manager): def test_lcbc010_side_updates(dash_duo, manager): with setup_long_callback_app(manager, "app_side_update") as app: dash_duo.start_server(app) - with app.test_lock: - dash_duo.find_element("#run-button").click() + dash_duo.find_element("#run-button").click() for i in range(1, 4): dash_duo.wait_for_text_to_equal("#side-status", f"Side Progress {i}/4") @@ -512,8 +510,7 @@ def test_lcbc011_long_pattern_matching(dash_duo, manager): dash_duo.start_server(app) for i in range(1, 4): for _ in range(i): - with app.test_lock: - dash_duo.find_element(f"button:nth-child({i})").click() + dash_duo.find_element(f"button:nth-child({i})").click() dash_duo.wait_for_text_to_equal("#result", f"Clicked '{i}'") @@ -521,8 +518,7 @@ def test_lcbc011_long_pattern_matching(dash_duo, manager): def test_lcbc012_long_callback_ctx(dash_duo, manager): with setup_long_callback_app(manager, "app_callback_ctx") as app: dash_duo.start_server(app) - with app.test_lock: - dash_duo.find_element("button:nth-child(1)").click() + dash_duo.find_element("button:nth-child(1)").click() dash_duo.wait_for_text_to_equal("#running", "off") output = json.loads(dash_duo.find_element("#result").text) From ac3cb3191ebbc64521f25546ff5d2ea81c293562 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 6 Jul 2022 15:35:24 -0400 Subject: [PATCH 6/6] Fix test app error. --- tests/integration/long_callback/app_error.py | 6 ++---- .../integration/long_callback/test_basic_long_callback.py | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/integration/long_callback/app_error.py b/tests/integration/long_callback/app_error.py index 23c3bc4b6e..9e59fa121e 100644 --- a/tests/integration/long_callback/app_error.py +++ b/tests/integration/long_callback/app_error.py @@ -1,4 +1,3 @@ -import os import time import dash @@ -36,9 +35,7 @@ prevent_initial_call=True, ) def callback(n_clicks): - if os.getenv("LONG_CALLBACK_MANAGER") != "celery": - # Diskmanager needs some time, celery takes too long. - time.sleep(1) + time.sleep(1) if n_clicks == 2: raise Exception("bad error") @@ -57,6 +54,7 @@ def callback(n_clicks): prevent_initial_call=True, ) def long_multi(n_clicks): + time.sleep(1) return ( [f"Updated: {n_clicks}"] + [i for i in range(1, n_clicks + 1)] diff --git a/tests/integration/long_callback/test_basic_long_callback.py b/tests/integration/long_callback/test_basic_long_callback.py index 70cc821019..3414c6ea1f 100644 --- a/tests/integration/long_callback/test_basic_long_callback.py +++ b/tests/integration/long_callback/test_basic_long_callback.py @@ -440,12 +440,11 @@ def test_lcbc008_long_callbacks_error(dash_duo, manager): dev_tools_ui=True, ) - clicker = dash_duo.find_element("#button") + clicker = dash_duo.wait_for_element("#button") def click_n_wait(): - with app.test_lock: - clicker.click() - dash_duo.wait_for_element("#button:disabled") + clicker.click() + dash_duo.wait_for_element("#button:disabled") dash_duo.wait_for_element("#button:not([disabled])") clicker.click()