Repository: seequent/pure_interface

Issue:
Adding interface to a subclass of a subclass causes properties to be called
The following causes the `next` property to be executed when the instance is created, leading to a tb due to infinite recursion. 

```python

import pure_interface


class IA(pure_interface.PureInterface):
    next = None


class IOther(IA):
    pass


class A(object, IA):
    @property
    def next(self):
        print("next")
        return self.__class__()


class B(A):
    pass


class C(B, IOther):
    pass


C()
```
The property isn't every executed if `C` class definition is changed to either `class C(A, IOther):` or `class C(B):`.

Discussion:

Patch:
diff --git a/pure_interface.py b/pure_interface.py
index 10fcb35..36ad611 100644
--- a/pure_interface.py
+++ b/pure_interface.py
@@ -630,7 +630,8 @@ class PureInterfaceType(abc.ABCMeta):
             raise InterfaceError('Interfaces cannot be instantiated')
         self = super(PureInterfaceType, cls).__call__(*args, **kwargs)
         for attr in cls._pi.abstractproperties:
-            if not hasattr(self, attr):
+            if not (hasattr(cls, attr) or hasattr(self, attr)):
+                # check for attribute on class first so that properties are not run.
                 raise InterfaceError('{}.__init__ does not create required attribute "{}"'.format(cls.__name__, attr))
         return self
 


Test patch:
diff --git a/tests/test_implementation_checks.py b/tests/test_implementation_checks.py
index a5442dc..6a367d3 100644
--- a/tests/test_implementation_checks.py
+++ b/tests/test_implementation_checks.py
@@ -543,6 +543,27 @@ class TestAttributeImplementations(unittest.TestCase):
 
         self.assertEqual(frozenset(), Function._pi.abstractproperties)
 
+    def test_property_not_accessed(self):
+        # previously instantiating C failed (issue 65)
+        class IA(pure_interface.PureInterface):
+            next = None
+
+        class IOther(IA):
+            pass
+
+        class A(IA, object):
+            @property
+            def next(self):
+                raise RuntimeError('property accessed')
+
+        class B(A):
+            pass
+
+        class C(B, IOther):
+            pass
+
+        C()
+
 
 class TestCrossImplementations(unittest.TestCase):
     """ test class attributes implemented as properties and vice versa """