Repository: plotly/dash

Issue:
Raise an exception when initializing callbacks with circular dependencies
```
@app.callback(Output('my-graph', 'figure'), [Input('my-graph', 'figure')])
```

Our users do this pretty often. For example, see here: https://community.plot.ly/t/object-as-input-output-error-loading-dependencies/12492


Discussion:
Hi, I would like to help on this one. Does this look like what you have in mind?
[https://github.com/chuyachia/dash/blob/callback-circular-deps-exception/dash/dash.py#L568-L578](https://github.com/chuyachia/dash/blob/callback-circular-deps-exception/dash/dash.py#L568-L578)
This is kind of related to https://github.com/plotly/dash-renderer/issues/76
If an exception is going to be raised by default, there should be a way to suppress it in the config to allow for circular dependencies

Resolving patch:
diff --git a/dash/dash.py b/dash/dash.py
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -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:
+                raise exceptions.SameInputOutputException(
+                    'Same output and input: {}'.format(output)
+                )
+
         if (layout is None and
                 not self.config.first('suppress_callback_exceptions',
                                       'supress_callback_exceptions')):
diff --git a/dash/dependencies.py b/dash/dependencies.py
--- a/dash/dependencies.py
+++ b/dash/dependencies.py
@@ -1,19 +1,34 @@
-# pylint: disable=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 isinstance(other, DashDependency) and str(self) == str(other)
+
+    def __hash__(self):
+        return hash(str(self))
+
 
 # pylint: disable=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=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=too-few-public-methods
+class State(DashDependency):
+    """Use the value of a state in a callback but don't trigger updates."""
diff --git a/dash/exceptions.py b/dash/exceptions.py
--- a/dash/exceptions.py
+++ b/dash/exceptions.py
@@ -76,3 +76,7 @@ class DependencyException(DashException):
 
 class ResourceException(DashException):
     pass
+
+
+class SameInputOutputException(CallbackException):
+    pass


Test patch:
diff --git a/tests/IntegrationTests.py b/tests/IntegrationTests.py
--- a/tests/IntegrationTests.py
+++ b/tests/IntegrationTests.py
@@ -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():
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -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
 
@@ -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]
+        )