Skip to content

Instantly share code, notes, and snippets.

@andres-fr
Created October 19, 2017 04:45
Show Gist options
  • Save andres-fr/763fa26a544f55982c3915271bc79ca4 to your computer and use it in GitHub Desktop.
Save andres-fr/763fa26a544f55982c3915271bc79ca4 to your computer and use it in GitHub Desktop.
This file performs some basic interfacing in Python 2, tested via unittest
"""This file performs some basic interfacing in Python 2:
1. Defining the methods that have to be overriden with the ABC module
2. Defining an input and output signature for those methods
3. Testing the validity of different implementations via unittest
Note the following:
!! In order to check the input/output signatures, the overriden methods have to be run once
at construction, which may be undesirable (usually this kind of interfacing happens within
a protocol that can take care of that).
"""
from __future__ import print_function, division
from abc import ABCMeta, abstractmethod
import unittest
####################################################################################################
### DEFINE THE INTERFACE
####################################################################################################
class MaybeBadInputSignatureError(Exception):
"""Expected to be raised if the call to a function raises a TypeError, most probably due
to a bad input signature. Due to its 'maybe-ness', the original error message can be
optionally displayed as well.
"""
def __init__(self, method_name, input_signature, original_errmsg=""):
orig_errmsg = "\n(Original error message: %s)"%original_errmsg if original_errmsg else ""
Exception.__init__(self, ("In %s method: did you respect the parameter signature %s?"+
orig_errmsg) % (method_name, str(input_signature)))
class BadOutputSignatureError(Exception):
"""Expected to be raised if the output signature of a method doesn't fit the expected one.
"""
def __init__(self, method_name, expected_signature, output_signature):
Exception.__init__(self,"Expected return value of %s method was %s, but got %s." %
(method_name, str(expected_signature), str(output_signature)))
class MyInterface:
"""This class acts like an interface: it cannot be instantiated because it has an abstract
method that has to be implemented by any class that inherits from it. The check methods raise
the corresponding exception if the input or output signatures of the implemented method don't
match the ones specified in the signatures.
IMPORTANT: keep in mind that, in order to check the input and output signatures, the
overriden method is run at instantiation, which may be undesirable.
"""
__metaclass__ = ABCMeta
def __init__(self):
self.input_signature = [int, int, str]
self.output_signature = [int, str]
self.__check_method_output(self.__check_method_input())
def __check_method_input(self):
try:
return self.override_this(*[x() for x in self.input_signature])
except TypeError as err:
raise MaybeBadInputSignatureError(self.__class__.__name__+".override_this()",
self.input_signature, str(err))
def __check_method_output(self, out):
implemented_out_sig = [type(x) for x in out]
if implemented_out_sig != self.output_signature:
raise BadOutputSignatureError(self.__class__.__name__+".override_this()",
self.output_signature,
implemented_out_sig)
@abstractmethod
def override_this(self, *args, **kwargs):
raise NotImplementedError
####################################################################################################
### DEFINE SOME TEST IMPLEMENTATIONS FOR THE INTERFACE
####################################################################################################
class GoodImplementation(MyInterface):
"""This is a correct implementation of the interface:
1. The Class extends the interface explicitly and implements the required methods
2. The input and output signature of the methods comply with the interface
"""
def override_this(self, int1, int2, str1):
return 123, "successfully overriden!"
class NoInterface(object):
"""This class complies with the interface (methods implemented and their signature) but
it doesn't extend it explicitly, which may be undesirable. This can be checked with
'isinstance(NoInterface, MyInterface)' as it can be seen in the unit tests.
"""
def override_this(self, int1, int2, str1):
return 123, "this wasn't overriden!"
class NoImplementation(MyInterface):
"""This class doesn't provide an implementation for all the abstract methods in the interface.
Trying to instantiate this raises a 'TypeError: Can't instantiate abstract class with
abstract methods'.
"""
pass
class NoActualImplementation(MyInterface):
"""This function does implement and override the interface, but no actual functionality is
added to the method, so trying to instantiate this still raises the 'NotImplementedError'.
"""
def override_this(self, int1, int2, str1):
super(NoActualImplementation, self).override_this()
class BadInputImplementation(MyInterface):
"""As defined in the interface, trying to instantiate this class with an incompatible parameter
list for the overriden method will raise a MaybeBadInputSignatureError exception.
"""
def override_this(self, int1, str1):
return 123, "a parameter is missing!"
class BadOutputImplementation(MyInterface):
"""If the signature of the overriden method's return value doesn't comply with the interface,
a BadOutputSignatureError will be raised.
"""
def override_this(self, int1, int2, str1):
return "return elements are switched!", 123
####################################################################################################
### UNIT TESTING
####################################################################################################
class UnitTestCase(unittest.TestCase):
def test_good_implementation(self):
"""This test should pass with no problems, as it complies with the interface specifications.
"""
x = GoodImplementation()
self.assertEqual((123, "successfully overriden!"), x.override_this(1,2,"3"))
def test_no_interface(self):
"""This object compiles, and would work since it implements the needed functionality, but it
doesn't extend the interface explicitly. This can be checked with 'isinstance'
"""
x = NoInterface()
self.assertEqual(False, isinstance(x, MyInterface))
def test_no_implementation(self):
"""Raises the following error, due to unimplemented abstract methods:
TypeError: Can't instantiate abstract class NoImplementation with abstract methods
"""
self.assertRaises(TypeError, NoImplementation)
def test_no_actual_implementation(self):
"""Raises a NotImplementedError error, since the method hasn't been actually overriden.
"""
self.assertRaises(NotImplementedError, NoActualImplementation)
def test_bad_input(self):
"""This raises the following exception, due to non-compliant input signature:
MaybeBadInputSignatureError: In BadInputImplementation.override_this() method: did you
respect the parameter signature [<type 'int'>, <type 'int'>, <type 'str'>]?
(Original error message: override_this() takes exactly 3 arguments (4 given))
"""
self.assertRaises(MaybeBadInputSignatureError, BadInputImplementation)
def test_bad_output(self):
"""This raises the following exception, due to non-compliant output signature:
BadOutputSignatureError: Return value of BadOutputImplementation.override_this()
method was [<type 'str'>, <type 'int'>], but [<type 'int'>, <type 'str'>] was expected.
"""
self.assertRaises(BadOutputSignatureError, BadOutputImplementation)
# run the tests!
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment