diff --git a/CHANGELOG.md b/CHANGELOG.md index b08439a695..7fbc913ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## [UNRELEASED] +### Fixed +- [#1384](https://github.com/plotly/dash/pull/1384) Fixed a bug introduced by [#1180](https://github.com/plotly/dash/pull/1180) breaking use of `prevent_initial_call` as a positional arg in callback definitions + ## [1.15.0] - 2020-08-25 ### Added - [#1355](https://github.com/plotly/dash/pull/1355) Removed redundant log message and consolidated logger initialization. You can now control the log level - for example suppress informational messages from Dash with `app.logger.setLevel(logging.WARNING)`. diff --git a/dash/dependencies.py b/dash/dependencies.py index 3c72d3867b..857b19f877 100644 --- a/dash/dependencies.py +++ b/dash/dependencies.py @@ -156,7 +156,7 @@ def handle_callback_args(args, kwargs): """Split args into outputs, inputs and states""" prevent_initial_call = kwargs.get("prevent_initial_call", None) if prevent_initial_call is None and args and isinstance(args[-1], bool): - prevent_initial_call = args.pop() + args, prevent_initial_call = args[:-1], args[-1] # flatten args, to support the older syntax where outputs, inputs, and states # each needed to be in their own list diff --git a/requires-testing.txt b/requires-testing.txt index 1aa667c03d..1662b98057 100644 --- a/requires-testing.txt +++ b/requires-testing.txt @@ -6,6 +6,7 @@ pytest-mock==2.0.0;python_version=="2.7" lxml==4.5.0 selenium==3.141.0 percy==2.0.2 +cryptography==3.0 requests[security]==2.21.0 beautifulsoup4==4.8.2 waitress==1.4.3 diff --git a/tests/integration/callbacks/test_prevent_initial.py b/tests/integration/callbacks/test_prevent_initial.py index adcef5b43e..8dba936145 100644 --- a/tests/integration/callbacks/test_prevent_initial.py +++ b/tests/integration/callbacks/test_prevent_initial.py @@ -333,3 +333,21 @@ def test_cbpi003_multi_outputs(flavor, dash_duo): dash_duo.wait_for_text_to_equal("#c", "BlueCheese") dash_duo.wait_for_text_to_equal("#b", "Cheese") dash_duo.wait_for_text_to_equal("#a", "Blue") + + +def test_cbpi004_positional_arg(dash_duo): + app = dash.Dash(__name__) + app.layout = html.Div([html.Button("click", id="btn"), html.Div(id="out")]) + + @app.callback(Output("out", "children"), Input("btn", "n_clicks"), True) + def f(n): + return n + + dash_duo.start_server(app) + dash_duo._wait_for_callbacks() + + dash_duo.wait_for_text_to_equal("#out", "") + + dash_duo.find_element("#btn").click() + + dash_duo.wait_for_text_to_equal("#out", "1") diff --git a/tests/integration/callbacks/test_validation.py b/tests/integration/callbacks/test_validation.py index 61acaae0f9..d049e21be3 100644 --- a/tests/integration/callbacks/test_validation.py +++ b/tests/integration/callbacks/test_validation.py @@ -193,3 +193,33 @@ def o2(i, s): dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#out1", "1: High") dash_duo.wait_for_text_to_equal("#out2", "2: High") + + +def test_cbva005_tuple_args(dash_duo): + app = Dash(__name__) + app.layout = html.Div( + [ + html.Div("Yo", id="in1"), + html.Div("lo", id="in2"), + html.Div(id="out1"), + html.Div(id="out2"), + ] + ) + + @app.callback( + Output("out1", "children"), (Input("in1", "children"), Input("in2", "children")) + ) + def f(i1, i2): + return "1: " + i1 + i2 + + @app.callback( + (Output("out2", "children"),), + Input("in1", "children"), + (State("in2", "children"),), + ) + def g(i1, i2): + return ("2: " + i1 + i2,) + + dash_duo.start_server(app) + dash_duo.wait_for_text_to_equal("#out1", "1: Yolo") + dash_duo.wait_for_text_to_equal("#out2", "2: Yolo") diff --git a/tests/integration/renderer/test_request_hooks.py b/tests/integration/renderer/test_request_hooks.py new file mode 100644 index 0000000000..5c7c574ad4 --- /dev/null +++ b/tests/integration/renderer/test_request_hooks.py @@ -0,0 +1,190 @@ +import json + +import dash_html_components as html +import dash_core_components as dcc +from dash import Dash +from dash.dependencies import Output, Input + + +def test_rdrh001_request_hooks(dash_duo): + app = Dash(__name__) + + app.index_string = """ + + + {%metas%} + {%title%} + {%favicon%} + {%css%} + + +
Testing custom DashRenderer
+ {%app_entry%} + +
With request hooks
+ + """ + + app.layout = html.Div( + [ + dcc.Input(id="input", value="initial value"), + html.Div( + html.Div( + [ + html.Div(id="output-1"), + html.Div(id="output-pre"), + html.Div(id="output-pre-payload"), + html.Div(id="output-post"), + html.Div(id="output-post-payload"), + html.Div(id="output-post-response"), + ] + ) + ), + ] + ) + + @app.callback(Output("output-1", "children"), [Input("input", "value")]) + def update_output(value): + return value + + dash_duo.start_server(app) + + _in = dash_duo.find_element("#input") + dash_duo.clear_input(_in) + + _in.send_keys("fire request hooks") + + dash_duo.wait_for_text_to_equal("#output-1", "fire request hooks") + dash_duo.wait_for_text_to_equal("#output-pre", "request_pre changed this text!") + dash_duo.wait_for_text_to_equal("#output-post", "request_post changed this text!") + + assert json.loads(dash_duo.find_element("#output-pre-payload").text) == { + "output": "output-1.children", + "outputs": {"id": "output-1", "property": "children"}, + "changedPropIds": ["input.value"], + "inputs": [{"id": "input", "property": "value", "value": "fire request hooks"}], + } + + assert json.loads(dash_duo.find_element("#output-post-payload").text) == { + "output": "output-1.children", + "outputs": {"id": "output-1", "property": "children"}, + "changedPropIds": ["input.value"], + "inputs": [{"id": "input", "property": "value", "value": "fire request hooks"}], + } + + assert json.loads(dash_duo.find_element("#output-post-response").text) == { + "output-1": {"children": "fire request hooks"} + } + + dash_duo.percy_snapshot(name="request-hooks render") + + +def test_rdrh002_with_custom_renderer_interpolated(dash_duo): + + renderer = """ + + """ + + class CustomDash(Dash): + def interpolate_index(self, **kwargs): + return """ + + + My App + + + +
My custom header
+ {app_entry} + {config} + {scripts} + {renderer} + + + """.format( + app_entry=kwargs["app_entry"], + config=kwargs["config"], + scripts=kwargs["scripts"], + renderer=renderer, + ) + + app = CustomDash() + + app.layout = html.Div( + [ + dcc.Input(id="input", value="initial value"), + html.Div( + html.Div( + [ + html.Div(id="output-1"), + html.Div(id="output-pre"), + html.Div(id="output-post"), + ] + ) + ), + ] + ) + + @app.callback(Output("output-1", "children"), [Input("input", "value")]) + def update_output(value): + return value + + dash_duo.start_server(app) + + input1 = dash_duo.find_element("#input") + dash_duo.clear_input(input1) + + input1.send_keys("fire request hooks") + + dash_duo.wait_for_text_to_equal("#output-1", "fire request hooks") + assert dash_duo.find_element("#output-pre").text == "request_pre was here!" + assert dash_duo.find_element("#output-post").text == "request_post!!!" + + dash_duo.percy_snapshot(name="request-hooks interpolated") diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index c9dbe1a657..fbd3ad4591 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -222,158 +222,6 @@ def create_layout(): assert dash_duo.find_element("#a").text == "Hello World" -def test_inin014_with_custom_renderer(dash_duo): - app = Dash(__name__) - - app.index_string = """ - - - {%metas%} - {%title%} - {%favicon%} - {%css%} - - -
Testing custom DashRenderer
- {%app_entry%} - -
With request hooks
- - """ - - app.layout = html.Div( - [ - dcc.Input(id="input", value="initial value"), - html.Div( - html.Div( - [ - html.Div(id="output-1"), - html.Div(id="output-pre"), - html.Div(id="output-post"), - ] - ) - ), - ] - ) - - @app.callback(Output("output-1", "children"), [Input("input", "value")]) - def update_output(value): - return value - - dash_duo.start_server(app) - - input1 = dash_duo.find_element("#input") - dash_duo.clear_input(input1) - - input1.send_keys("fire request hooks") - - dash_duo.wait_for_text_to_equal("#output-1", "fire request hooks") - assert dash_duo.find_element("#output-pre").text == "request_pre!!!" - assert dash_duo.find_element("#output-post").text == "request_post ran!" - - dash_duo.percy_snapshot(name="request-hooks intg") - - -def test_inin015_with_custom_renderer_interpolated(dash_duo): - - renderer = """ - - """ - - class CustomDash(Dash): - def interpolate_index(self, **kwargs): - return """ - - - My App - - - -
My custom header
- {app_entry} - {config} - {scripts} - {renderer} - - - """.format( - app_entry=kwargs["app_entry"], - config=kwargs["config"], - scripts=kwargs["scripts"], - renderer=renderer, - ) - - app = CustomDash() - - app.layout = html.Div( - [ - dcc.Input(id="input", value="initial value"), - html.Div( - html.Div( - [ - html.Div(id="output-1"), - html.Div(id="output-pre"), - html.Div(id="output-post"), - ] - ) - ), - ] - ) - - @app.callback(Output("output-1", "children"), [Input("input", "value")]) - def update_output(value): - return value - - dash_duo.start_server(app) - - input1 = dash_duo.find_element("#input") - dash_duo.clear_input(input1) - - input1.send_keys("fire request hooks") - - dash_duo.wait_for_text_to_equal("#output-1", "fire request hooks") - assert dash_duo.find_element("#output-pre").text == "request_pre was here!" - assert dash_duo.find_element("#output-post").text == "request_post!!!" - - dash_duo.percy_snapshot(name="request-hooks interpolated") - - def test_inin017_late_component_register(dash_duo): app = Dash() diff --git a/tests/integration/test_render.py b/tests/integration/test_render.py index 1a79090323..f54d123bc1 100644 --- a/tests/integration/test_render.py +++ b/tests/integration/test_render.py @@ -2,8 +2,6 @@ import time import json from multiprocessing import Value -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC @@ -779,126 +777,6 @@ def update_output(*args): self.assertEqual(call_count.value, 4) self.percy_snapshot("button-2 click again") - def test_request_hooks(self): - app = Dash(__name__) - - app.index_string = """ - - - {%metas%} - {%title%} - {%favicon%} - {%css%} - - -
Testing custom DashRenderer
- {%app_entry%} - -
With request hooks
- - """ - - app.layout = html.Div( - [ - dcc.Input(id="input", value="initial value"), - html.Div( - html.Div( - [ - html.Div(id="output-1"), - html.Div(id="output-pre"), - html.Div(id="output-pre-payload"), - html.Div(id="output-post"), - html.Div(id="output-post-payload"), - html.Div(id="output-post-response"), - ] - ) - ), - ] - ) - - @app.callback(Output("output-1", "children"), [Input("input", "value")]) - def update_output(value): - return value - - self.startServer(app) - - input1 = self.wait_for_element_by_css_selector("#input") - initialValue = input1.get_attribute("value") - - action = ActionChains(self.driver) - action.click(input1) - action = action.send_keys(Keys.BACKSPACE * len(initialValue)) - - action.send_keys("fire request hooks").perform() - - self.wait_for_text_to_equal("#output-1", "fire request hooks") - self.wait_for_text_to_equal("#output-pre", "request_pre changed this text!") - self.wait_for_text_to_equal("#output-post", "request_post changed this text!") - pre_payload = self.wait_for_element_by_css_selector("#output-pre-payload").text - post_payload = self.wait_for_element_by_css_selector( - "#output-post-payload" - ).text - post_response = self.wait_for_element_by_css_selector( - "#output-post-response" - ).text - self.assertEqual( - json.loads(pre_payload), - { - "output": "output-1.children", - "outputs": {"id": "output-1", "property": "children"}, - "changedPropIds": ["input.value"], - "inputs": [ - {"id": "input", "property": "value", "value": "fire request hooks"} - ], - }, - ) - self.assertEqual( - json.loads(post_payload), - { - "output": "output-1.children", - "outputs": {"id": "output-1", "property": "children"}, - "changedPropIds": ["input.value"], - "inputs": [ - {"id": "input", "property": "value", "value": "fire request hooks"} - ], - }, - ) - self.assertEqual( - json.loads(post_response), {"output-1": {"children": "fire request hooks"}} - ) - self.percy_snapshot(name="request-hooks render") - def test_graphs_in_tabs_do_not_share_state(self): app = Dash()