Decorator to be used on functions with annotations. Runs type checks to enforce annotations. Raises
    :class:`EnsureError` if any argument passed to *f* is not of the type specified by the annotation. Also raises
    :class:`EnsureError` if the return value of *f* is not of the type specified by the annotation. Examples:

    .. code-block:: python

        from ensure import ensure_annotations

        @ensure_annotations
        def f(x: int, y: float) -> float:
            return x+y

        print(f(1, y=2.2))

        >>> 3.2

        print(f(1, y=2))

        >>> ensure.EnsureError: Argument y of type <class 'int'> to
        <function f at 0x109b7c710> does not match annotation type <class 'float'>

def ensure_annotations(f):
    """
    Decorator to be used on functions with annotations. Runs type checks to enforce annotations. Raises
    :class:`EnsureError` if any argument passed to *f* is not of the type specified by the annotation. Also raises
    :class:`EnsureError` if the return value of *f* is not of the type specified by the annotation. Examples:

    .. code-block:: python

        from ensure import ensure_annotations

        @ensure_annotations
        def f(x: int, y: float) -> float:
            return x+y

        print(f(1, y=2.2))

        >>> 3.2

        print(f(1, y=2))

        >>> ensure.EnsureError: Argument y of type <class 'int'> to
        <function f at 0x109b7c710> does not match annotation type <class 'float'>
    """

    if f.__defaults__:
        for rpos, value in enumerate(f.__defaults__):
            pos = f.__code__.co_argcount - len(f.__defaults__) + rpos
            arg = f.__code__.co_varnames[pos]
            _check_default_argument(f, arg, value)
    if f.__kwdefaults__:
        for arg, value in f.__kwdefaults__.items():
            _check_default_argument(f, arg, value)

    arg_properties = []
    for pos, arg in enumerate(f.__code__.co_varnames):
        if pos >= f.__code__.co_argcount + f.__code__.co_kwonlyargcount:
            break
        elif arg in f.__annotations__:
            templ = f.__annotations__[arg]
            if pos >= f.__code__.co_argcount:
                arg_properties.append((arg, templ, None))
            else:
                arg_properties.append((arg, templ, pos))

    if 'return' in f.__annotations__:
        return_templ = f.__annotations__['return']
        return WrappedFunctionReturn(arg_properties, f, return_templ)
    else:
        return WrappedFunction(arg_properties, f)