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
+
+
+
+
+ {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
-
-
-
-
- {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()