Repository: plotly/dash

Issue:
prevent_initial_call in callback throws exception [BUG]
Thank you so much for helping improve the quality of Dash!

We do our best to catch bugs during the release process, but we rely on your help to find the ones that slip through.


**Describe your context**
Please provide us your environment so we can easily reproduce the issue.

-  replace the result of `pip list | grep dash` below
```
dash                      1.15.0
dash-bootstrap-components 0.9.2
dash-core-components      1.11.0
dash-html-components      1.1.0
dash-renderer             1.7.0
dash-table                4.10.0
```

**Describe the bug**

With the release of Dash 1.15.0. Setting prevent_initial_call in a callback now throws an exception.
It appears prevent_initial_call must be explicitly provided as a named parameter ex. prevent_initial_call=True. This was not previously the case if all parameters (Output, Input, State, and prevent_initial_call) were provided.

  File "C:\Program Files\Python36\lib\site-packages\dash\dash.py", line 980, in callback
    _args, _kwargs
  File "C:\Program Files\Python36\lib\site-packages\dash\dependencies.py", line 159, in handle_callback_args
    prevent_initial_call = args.pop()
AttributeError: 'tuple' object has no attribute 'pop' 

**Expected behavior**

An exception should not be thrown.


prevent_initial_call in callback throws exception [BUG]
Thank you so much for helping improve the quality of Dash!

We do our best to catch bugs during the release process, but we rely on your help to find the ones that slip through.


**Describe your context**
Please provide us your environment so we can easily reproduce the issue.

-  replace the result of `pip list | grep dash` below
```
dash                      1.15.0
dash-bootstrap-components 0.9.2
dash-core-components      1.11.0
dash-html-components      1.1.0
dash-renderer             1.7.0
dash-table                4.10.0
```

**Describe the bug**

With the release of Dash 1.15.0. Setting prevent_initial_call in a callback now throws an exception.
It appears prevent_initial_call must be explicitly provided as a named parameter ex. prevent_initial_call=True. This was not previously the case if all parameters (Output, Input, State, and prevent_initial_call) were provided.

  File "C:\Program Files\Python36\lib\site-packages\dash\dash.py", line 980, in callback
    _args, _kwargs
  File "C:\Program Files\Python36\lib\site-packages\dash\dependencies.py", line 159, in handle_callback_args
    prevent_initial_call = args.pop()
AttributeError: 'tuple' object has no attribute 'pop' 

**Expected behavior**

An exception should not be thrown.




Discussion:
Thanks @rpmchale - confirmed. I think I see another issue in `handle_callback_args` as well. We'll get this fixed! 
Thanks @rpmchale - confirmed. I think I see another issue in `handle_callback_args` as well. We'll get this fixed! 

Resolving patch:
diff --git a/dash/dependencies.py b/dash/dependencies.py
--- 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


Test patch:
diff --git a/tests/integration/callbacks/test_prevent_initial.py b/tests/integration/callbacks/test_prevent_initial.py
--- 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
--- 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
--- /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 = """<!DOCTYPE html>
+    <html>
+        <head>
+            {%metas%}
+            <title>{%title%}</title>
+            {%favicon%}
+            {%css%}
+        </head>
+        <body>
+            <div>Testing custom DashRenderer</div>
+            {%app_entry%}
+            <footer>
+                {%config%}
+                {%scripts%}
+                <script id="_dash-renderer" type"application/json">
+                    const renderer = new DashRenderer({
+                        request_pre: (payload) => {
+                            var output = document.getElementById('output-pre')
+                            var outputPayload = document.getElementById('output-pre-payload')
+                            if(output) {
+                                output.innerHTML = 'request_pre changed this text!';
+                            }
+                            if(outputPayload) {
+                                outputPayload.innerHTML = JSON.stringify(payload);
+                            }
+                        },
+                        request_post: (payload, response) => {
+                            var output = document.getElementById('output-post')
+                            var outputPayload = document.getElementById('output-post-payload')
+                            var outputResponse = document.getElementById('output-post-response')
+                            if(output) {
+                                output.innerHTML = 'request_post changed this text!';
+                            }
+                            if(outputPayload) {
+                                outputPayload.innerHTML = JSON.stringify(payload);
+                            }
+                            if(outputResponse) {
+                                outputResponse.innerHTML = JSON.stringify(response);
+                            }
+                        }
+                    })
+                </script>
+            </footer>
+            <div>With request hooks</div>
+        </body>
+    </html>"""
+
+    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 = """
+        <script id="_dash-renderer" type="application/javascript">
+            console.log('firing up a custom renderer!')
+            const renderer = new DashRenderer({
+                request_pre: () => {
+                    var output = document.getElementById('output-pre')
+                    if(output) {
+                        output.innerHTML = 'request_pre was here!';
+                    }
+                },
+                request_post: () => {
+                    var output = document.getElementById('output-post')
+                    if(output) {
+                        output.innerHTML = 'request_post!!!';
+                    }
+                }
+            })
+        </script>
+    """
+
+    class CustomDash(Dash):
+        def interpolate_index(self, **kwargs):
+            return """<!DOCTYPE html>
+            <html>
+                <head>
+                    <title>My App</title>
+                </head>
+                <body>
+
+                    <div id="custom-header">My custom header</div>
+                    {app_entry}
+                    {config}
+                    {scripts}
+                    {renderer}
+                    <div id="custom-footer">My custom footer</div>
+                </body>
+            </html>""".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
--- 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 = """<!DOCTYPE html>
-    <html>
-        <head>
-            {%metas%}
-            <title>{%title%}</title>
-            {%favicon%}
-            {%css%}
-        </head>
-        <body>
-            <div>Testing custom DashRenderer</div>
-            {%app_entry%}
-            <footer>
-                {%config%}
-                {%scripts%}
-                <script id="_dash-renderer" type="application/javascript">
-                    console.log('firing up a custom renderer!')
-                    const renderer = new DashRenderer({
-                        request_pre: () => {
-                            var output = document.getElementById('output-pre')
-                            if(output) {
-                                output.innerHTML = 'request_pre!!!';
-                            }
-                        },
-                        request_post: () => {
-                            var output = document.getElementById('output-post')
-                            if(output) {
-                                output.innerHTML = 'request_post ran!';
-                            }
-                        }
-                    })
-                </script>
-            </footer>
-            <div>With request hooks</div>
-        </body>
-    </html>"""
-
-    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 = """
-        <script id="_dash-renderer" type="application/javascript">
-            console.log('firing up a custom renderer!')
-            const renderer = new DashRenderer({
-                request_pre: () => {
-                    var output = document.getElementById('output-pre')
-                    if(output) {
-                        output.innerHTML = 'request_pre was here!';
-                    }
-                },
-                request_post: () => {
-                    var output = document.getElementById('output-post')
-                    if(output) {
-                        output.innerHTML = 'request_post!!!';
-                    }
-                }
-            })
-        </script>
-    """
-
-    class CustomDash(Dash):
-        def interpolate_index(self, **kwargs):
-            return """<!DOCTYPE html>
-            <html>
-                <head>
-                    <title>My App</title>
-                </head>
-                <body>
-
-                    <div id="custom-header">My custom header</div>
-                    {app_entry}
-                    {config}
-                    {scripts}
-                    {renderer}
-                    <div id="custom-footer">My custom footer</div>
-                </body>
-            </html>""".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
--- 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 = """<!DOCTYPE html>
-        <html>
-            <head>
-                {%metas%}
-                <title>{%title%}</title>
-                {%favicon%}
-                {%css%}
-            </head>
-            <body>
-                <div>Testing custom DashRenderer</div>
-                {%app_entry%}
-                <footer>
-                    {%config%}
-                    {%scripts%}
-                    <script id="_dash-renderer" type"application/json">
-                        const renderer = new DashRenderer({
-                            request_pre: (payload) => {
-                                var output = document.getElementById('output-pre')
-                                var outputPayload = document.getElementById('output-pre-payload')
-                                if(output) {
-                                    output.innerHTML = 'request_pre changed this text!';
-                                }
-                                if(outputPayload) {
-                                    outputPayload.innerHTML = JSON.stringify(payload);
-                                }
-                            },
-                            request_post: (payload, response) => {
-                                var output = document.getElementById('output-post')
-                                var outputPayload = document.getElementById('output-post-payload')
-                                var outputResponse = document.getElementById('output-post-response')
-                                if(output) {
-                                    output.innerHTML = 'request_post changed this text!';
-                                }
-                                if(outputPayload) {
-                                    outputPayload.innerHTML = JSON.stringify(payload);
-                                }
-                                if(outputResponse) {
-                                    outputResponse.innerHTML = JSON.stringify(response);
-                                }
-                            }
-                        })
-                    </script>
-                </footer>
-                <div>With request hooks</div>
-            </body>
-        </html>"""
-
-        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()