Repository: pyca/cryptography

Issue:
Need zope.interface.verify.verifyObject for ABCs.
Every object that claims to provide some interface should then use it in a unit test.



Discussion:
So `zope.interface` would become a dependency but only for testing?

I think @dreid is suggesting we build something equivilant, not actually
using z.i

On Thu, May 8, 2014 at 10:35 AM, Paul Kehrer notifications@github.comwrote:

> So zope.interface would become a dependency but only for testing?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/pyca/cryptography/issues/1024#issuecomment-42580393
> .

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

Correct.

This is terrible: https://gist.github.com/dreid/985295a04dc1cf134aa7


Resolving patch:
diff --git a/cryptography/utils.py b/cryptography/utils.py
--- a/cryptography/utils.py
+++ b/cryptography/utils.py
@@ -13,6 +13,8 @@
 
 from __future__ import absolute_import, division, print_function
 
+import abc
+import inspect
 import sys
 
 
@@ -21,6 +23,7 @@
 
 def register_interface(iface):
     def register_decorator(klass):
+        verify_interface(iface, klass)
         iface.register(klass)
         return klass
     return register_decorator
@@ -30,6 +33,30 @@ def read_only_property(name):
     return property(lambda self: getattr(self, name))
 
 
+class InterfaceNotImplemented(Exception):
+    pass
+
+
+def verify_interface(iface, klass):
+    for method in iface.__abstractmethods__:
+        if not hasattr(klass, method):
+            raise InterfaceNotImplemented(
+                "{0} is missing a {1!r} method".format(klass, method)
+            )
+        if isinstance(getattr(iface, method), abc.abstractproperty):
+            # Can't properly verify these yet.
+            continue
+        spec = inspect.getargspec(getattr(iface, method))
+        actual = inspect.getargspec(getattr(klass, method))
+        if spec != actual:
+            raise InterfaceNotImplemented(
+                "{0}.{1}'s signature differs from the expected. Expected: "
+                "{2!r}. Received: {3!r}".format(
+                    klass, method, spec, actual
+                )
+            )
+
+
 def bit_length(x):
     if sys.version_info >= (2, 7):
         return x.bit_length()


Test patch:
diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py
new file mode 100644
--- /dev/null
+++ b/tests/test_interfaces.py
@@ -0,0 +1,63 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import abc
+
+import pytest
+
+import six
+
+from cryptography.utils import InterfaceNotImplemented, verify_interface
+
+
+class TestVerifyInterface(object):
+    def test_verify_missing_method(self):
+        @six.add_metaclass(abc.ABCMeta)
+        class SimpleInterface(object):
+            @abc.abstractmethod
+            def method(self):
+                """A simple method"""
+
+        class NonImplementer(object):
+            pass
+
+        with pytest.raises(InterfaceNotImplemented):
+            verify_interface(SimpleInterface, NonImplementer)
+
+    def test_different_arguments(self):
+        @six.add_metaclass(abc.ABCMeta)
+        class SimpleInterface(object):
+            @abc.abstractmethod
+            def method(self, a):
+                """Method with one argument"""
+
+        class NonImplementer(object):
+            def method(self):
+                """Method with no arguments"""
+
+        with pytest.raises(InterfaceNotImplemented):
+            verify_interface(SimpleInterface, NonImplementer)
+
+    def test_handles_abstract_property(self):
+        @six.add_metaclass(abc.ABCMeta)
+        class SimpleInterface(object):
+            @abc.abstractproperty
+            def property(self):
+                """An abstract property"""
+
+        class NonImplementer(object):
+            @property
+            def property(self):
+                """A concrete property"""
+
+        verify_interface(SimpleInterface, NonImplementer)