diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa49dea96..50415e28d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,10 +38,12 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed -- [#2016](https://github.com/plotly/dash/pull/2016) Drop the 375px width from default percy_snapshot calls, keep only 1280px - - [#1751](https://github.com/plotly/dash/pull/1751) Rename `app.run_server` to `app.run` while preserving `app.run_server` for backwards compatibility. +- [#1839](https://github.com/plotly/dash/pull/1839) The `callback` decorator returns the original function, not the wrapped function, so that you can still call these functions directly, for example in tests. Note that in this case there will be no callback context so not all callbacks can be tested this way. + +- [#2016](https://github.com/plotly/dash/pull/2016) Drop the 375px width from default percy_snapshot calls, keep only 1280px + - [#2027](https://github.com/plotly/dash/pull/1751) Improve the error message when a user doesn't wrap children in a list ### Updated diff --git a/dash/_callback.py b/dash/_callback.py index 4d6035d295..7653b29f7b 100644 --- a/dash/_callback.py +++ b/dash/_callback.py @@ -196,7 +196,7 @@ def add_context(*args, **kwargs): callback_map[callback_id]["callback"] = add_context - return add_context + return func return wrap_func diff --git a/tests/integration/callbacks/test_basic_callback.py b/tests/integration/callbacks/test_basic_callback.py index 0d23f6391e..0a3a4d59ee 100644 --- a/tests/integration/callbacks/test_basic_callback.py +++ b/tests/integration/callbacks/test_basic_callback.py @@ -391,7 +391,7 @@ def test_cbsc008_wildcard_prop_callbacks(dash_duo): "string", html.Div( id="output-1", - **{"data-cb": "initial value", "aria-cb": "initial value"} + **{"data-cb": "initial value", "aria-cb": "initial value"}, ), ] ) @@ -765,3 +765,22 @@ def update_output(value, data): assert store_data.value == 123 assert dash_duo.get_logs() == [] + + +def test_cbsc017_callback_directly_callable(): + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Input(id="input", value="initial value"), + html.Div(html.Div([1.5, None, "string", html.Div(id="output-1")])), + ] + ) + + @app.callback( + Output("output-1", "children"), + [Input("input", "value")], + ) + def update_output(value): + return f"returning {value}" + + assert update_output("my-value") == "returning my-value" diff --git a/tests/integration/callbacks/test_validation.py b/tests/integration/callbacks/test_validation.py index 4e3850e920..829cc4b009 100644 --- a/tests/integration/callbacks/test_validation.py +++ b/tests/integration/callbacks/test_validation.py @@ -77,10 +77,12 @@ def test_cbva002_callback_return_validation(): def single(a): return set([1]) + single_wrapped = app.callback_map["b.children"]["callback"] + with pytest.raises(InvalidCallbackReturnValue): # outputs_list (normally callback_context.outputs_list) is provided # by the dispatcher from the request. - single("aaa", outputs_list={"id": "b", "property": "children"}) + single_wrapped("aaa", outputs_list={"id": "b", "property": "children"}) pytest.fail("not serializable") @app.callback( @@ -89,12 +91,14 @@ def single(a): def multi(a): return [1, set([2])] + multi_wrapped = app.callback_map["..c.children...d.children.."]["callback"] + with pytest.raises(InvalidCallbackReturnValue): outputs_list = [ {"id": "c", "property": "children"}, {"id": "d", "property": "children"}, ] - multi("aaa", outputs_list=outputs_list) + multi_wrapped("aaa", outputs_list=outputs_list) pytest.fail("nested non-serializable") @app.callback( @@ -103,12 +107,14 @@ def multi(a): def multi2(a): return ["abc"] + multi2_wrapped = app.callback_map["..e.children...f.children.."]["callback"] + with pytest.raises(InvalidCallbackReturnValue): outputs_list = [ {"id": "e", "property": "children"}, {"id": "f", "property": "children"}, ] - multi2("aaa", outputs_list=outputs_list) + multi2_wrapped("aaa", outputs_list=outputs_list) pytest.fail("wrong-length list") diff --git a/tests/unit/dash/test_grouped_callbacks.py b/tests/unit/dash/test_grouped_callbacks.py index c7ecadde56..cdbe9dc124 100644 --- a/tests/unit/dash/test_grouped_callbacks.py +++ b/tests/unit/dash/test_grouped_callbacks.py @@ -1,5 +1,6 @@ import dash from dash._grouping import make_grouping_by_index, grouping_len, flatten_grouping +from dash._utils import create_callback_id from dash.dependencies import Input, State, Output, ClientsideFunction import mock import json @@ -39,12 +40,18 @@ def check_output_for_grouping(grouping): app = dash.Dash() mock_fn = mock.Mock() mock_fn.return_value = grouping + if multi: + callback_id = create_callback_id(flatten_grouping(outputs)) + else: + callback_id = create_callback_id(outputs) - wrapped_fn = app.callback( + app.callback( outputs, Input("input-a", "prop"), )(mock_fn) + wrapped_fn = app.callback_map[callback_id]["callback"] + expected_outputs = [ (dep.component_id, dep.component_property, val) for dep, val in zip(flatten_grouping(outputs), flatten_grouping(grouping)) @@ -96,11 +103,13 @@ def check_callback_inputs_for_grouping(grouping): mock_fn = mock.Mock() mock_fn.return_value = 23 - wrapped_fn = app.callback( + app.callback( Output("output-a", "prop"), inputs, )(mock_fn) + wrapped_fn = app.callback_map["output-a.prop"]["callback"] + flat_input_state_values = flatten_grouping(grouping) flat_input_values = flat_input_state_values[0::2] flat_state_values = flat_input_state_values[1::2]