Given a reference class or function check if an other class or function
    could be substituted without causing any instantiation/usage issues.

    :param object_name: the name of the object being checked.
    :type object_name: string
    :param reference_object: the reference class or function.
    :type reference_object: class/function
    :param other_object: the other class or function to be checked.
    :type other_object: class/function
    :raise AutodiscoveryCheckerError: in case the other object is not
        compatible with the reference object.

def check_signature(object_name, reference_object, other_object):
    """
    Given a reference class or function check if an other class or function
    could be substituted without causing any instantiation/usage issues.

    :param object_name: the name of the object being checked.
    :type object_name: string
    :param reference_object: the reference class or function.
    :type reference_object: class/function
    :param other_object: the other class or function to be checked.
    :type other_object: class/function
    :raise AutodiscoveryCheckerError: in case the other object is not
        compatible with the reference object.
    """
    try:
        if inspect.isclass(reference_object):
            # if the reference_object is a class
            if inspect.isclass(other_object):
                # if the other_object is a class
                if issubclass(other_object, reference_object):
                    # if the other_object is derived from the reference we
                    # should check for all the method in the former that
                    # exists in the the latter, wethever they recursively have
                    # the same signature.
                    reference_object_map = dict(
                        inspect.getmembers(reference_object,
                                           inspect.isroutine))
                    for other_method_name, other_method_code in \
                            inspect.getmembers(other_object,
                                               inspect.isroutine):
                        if other_method_name in reference_object_map:
                            check_signature(
                                object_name,
                                reference_object_map[other_method_name],
                                other_method_code)
                else:
                    # if the other_object is not derived from the
                    # reference_object then all the method declared in the
                    # latter should exist in the former and they should
                    # recursively have the same signature.
                    other_object_map = dict(
                        inspect.getmembers(other_object, inspect.isroutine))
                    for reference_method_name, reference_method_code in \
                        inspect.getmembers(
                            reference_object, inspect.isroutine):
                        if reference_method_name in other_object_map:
                            check_signature(
                                object_name, reference_method_code,
                                other_method_code)
                        else:
                            raise AutodiscoveryCheckerError(
                                '"%s", which'
                                ' exists in the reference class, does not'
                                ' exist in the other class, and the reference'
                                ' class is not an anchestor of the other' %
                                reference_method_name)
            else:
                # We are comparing apples and oranges!
                raise AutodiscoveryCheckerError(
                    "%s (the reference object)"
                    " is a class while %s (the other object) is not a class" %
                    (reference_object, other_object))
        elif inspect.isroutine(reference_object):
            # if the reference_object is a function
            if inspect.isroutine(other_object):
                # if the other_object is a function we will compare the
                # reference_object and other_object function signautre i.e.
                # their parameters.
                reference_args, reference_varargs, reference_varkw, \
                    reference_defaults = inspect.getargspec(reference_object)
                other_args, other_varargs, other_varkw, \
                    other_defaults = inspect.getargspec(other_object)
                # We normalize the reference_defaults to be a list
                if reference_defaults is not None:
                    reference_defaults = list(reference_defaults)
                else:
                    reference_defaults = []

                # We normalize the other_defaults to be a list
                if other_defaults is not None:
                    other_defaults = list(other_defaults)
                else:
                    other_defaults = []

                # Check for presence of missing parameters in other function
                if not (other_varargs or other_varkw):
                    for reference_arg in reference_args:
                        if reference_arg not in other_args:
                            raise AutodiscoveryCheckerError(
                                'Argument "%s"'
                                ' in reference function %s does not exist in'
                                ' the other function %s' %
                                (reference_arg, reference_object, other_object)
                            )

                # Check for presence of additional parameters in other
                # function
                if not (reference_varargs or reference_varkw):
                    for other_arg in other_args:
                        if other_arg not in reference_args:
                            raise AutodiscoveryCheckerError(
                                'Argument "%s"'
                                ' in other function %s does not exist in the'
                                ' reference function %s' %
                                (other_arg, other_object, reference_object))

                # Check sorting of arguments
                for reference_arg, other_arg in map(
                        None, reference_args, other_args):
                    if not((reference_arg == other_arg) or
                           (reference_arg is None and
                            (reference_varargs or reference_varkw)) or
                           (other_arg is None and
                            (other_args or other_varargs))):
                        raise AutodiscoveryCheckerError(
                            'Argument "%s" in'
                            ' the other function is in the position of'
                            ' argument "%s" in the reference function, i.e.'
                            ' the order of arguments is not respected' %
                            (other_arg, reference_arg))

                if len(reference_defaults) != len(other_defaults) and not (
                        reference_args or reference_varargs or
                        other_args or other_varargs):
                    raise AutodiscoveryCheckerError(
                        "Default parameters in"
                        " the other function are not corresponding to the"
                        " default of parameters of the reference function")
            else:
                # We are comparing apples and oranges!
                raise AutodiscoveryCheckerError(
                    '%s (the reference object)'
                    ' is a function while %s (the other object) is not a'
                    ' function' % (reference_object, other_object))
    except AutodiscoveryCheckerError as err:
        try:
            sourcefile = inspect.getsourcefile(other_object)
            sourceline = inspect.getsourcelines(other_object)[1]
        except IOError:
            # other_object is not loaded from a real file
            sourcefile = 'N/A'
            sourceline = 'N/A'
        raise AutodiscoveryCheckerError(
            'Error in checking signature for'
            ' "%s" as defined at "%s" (line %s): %s' %
            (object_name, sourcefile, sourceline, err))