Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise exception when same input & output are used in a callback #605

Merged
merged 11 commits into from
Feb 13, 2019
6 changes: 6 additions & 0 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,12 @@ def _validate_callback(self, output, inputs, state):
# pylint: disable=too-many-branches
layout = self._cached_layout or self._layout_value()

for i in inputs:
if output == i:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to need some changes for multi-output...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'll update once merged.

raise exceptions.SameInputOutputException(
'Same output and input: {}'.format(output)
)

if (layout is None and
not self.config.first('suppress_callback_exceptions',
'supress_callback_exceptions')):
Expand Down
34 changes: 24 additions & 10 deletions dash/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
# pylint: disable=old-style-class, too-few-public-methods
class Output:
class DashDependency:
def __init__(self, component_id, component_property):
self.component_id = component_id
self.component_property = component_property

def __str__(self):
return '{}.{}'.format(
self.component_id,
self.component_property
)

def __repr__(self):
return '<{} `{}`>'.format(self.__class__.__name__, self)

def __eq__(self, other):
return str(self) == str(other)

def __hash__(self):
return hash(str(self))

# pylint: disable=old-style-class, too-few-public-methods
class Input:
def __init__(self, component_id, component_property):
self.component_id = component_id
self.component_property = component_property
class Output(DashDependency):
"""Output of a callback."""


# pylint: disable=old-style-class, too-few-public-methods
class State:
def __init__(self, component_id, component_property):
self.component_id = component_id
self.component_property = component_property
class Input(DashDependency):
"""Input of callback trigger an update when it is updated."""


# pylint: disable=old-style-class, too-few-public-methods
class State(DashDependency):
"""Use the value of a state in a callback but don't trigger updates."""
4 changes: 4 additions & 0 deletions dash/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ class DependencyException(DashException):

class ResourceException(DashException):
pass


class SameInputOutputException(CallbackException):
pass
7 changes: 4 additions & 3 deletions tests/IntegrationTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ def setUp(s):
pass

def tearDown(s):
time.sleep(2)
s.server_process.terminate()
time.sleep(2)
if hasattr(s, 'server_process'):
time.sleep(2)
s.server_process.terminate()
time.sleep(2)

def startServer(s, dash):
def run():
Expand Down
20 changes: 19 additions & 1 deletion tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import dash

from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
from dash.exceptions import PreventUpdate, CallbackException
from .IntegrationTests import IntegrationTests
from .utils import assert_clean_console, invincible, wait_for

Expand Down Expand Up @@ -553,3 +553,21 @@ def update_output(value):
time.sleep(1)

self.wait_for_element_by_css_selector('#inserted-input')

def test_output_input_invalid_callback(self):
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div('child', id='input-output'),
html.Div(id='out')
])

with self.assertRaises(CallbackException) as context:
@app.callback(Output('input-output', 'children'),
[Input('input-output', 'children')])
def failure(children):
pass

self.assertEqual(
'Same output and input: input-output.children',
context.exception.args[0]
)