Apply interception on input target and return the final target.

    :param Callable target: target on applying the interception_fn.
    :param function interception_fn: interception function to apply on
        target
    :param ctx: target ctx (instance or class) if not None.

    :return: both interception and intercepted
        - if target is a builtin function,
            the result is a (wrapper function, builtin).
        - if target is a function, interception is target where
            code is intercepted code, and interception is a new function where
            code is target code.
    :rtype: tuple(callable, function, ctx)
    :raises: TypeError if target is not a routine.

def _apply_interception(
        target, interception_fn, ctx=None, _globals=None
):
    """Apply interception on input target and return the final target.

    :param Callable target: target on applying the interception_fn.
    :param function interception_fn: interception function to apply on
        target
    :param ctx: target ctx (instance or class) if not None.

    :return: both interception and intercepted
        - if target is a builtin function,
            the result is a (wrapper function, builtin).
        - if target is a function, interception is target where
            code is intercepted code, and interception is a new function where
            code is target code.
    :rtype: tuple(callable, function, ctx)
    :raises: TypeError if target is not a routine.
    """

    if not callable(target):
        raise TypeError('target {0} is not callable.'.format(target))

    intercepted = target
    interception = interception_fn

    # try to get the right ctx
    if ctx is None:
        ctx = find_ctx(elt=target)

    # if target is a builtin
    if isbuiltin(target) or getmodule(target) is builtins:
        # update builtin function reference in module with wrapper
        module = getmodule(target)
        found = False  # check for found function

        if module is not None:
            # update all references by value
            for name, _ in getmembers(
                    module, lambda member: member is target):
                setattr(module, name, interception_fn)
                found = True

            if not found:  # raise Exception if not found
                raise JoinpointError(
                    "Impossible to weave on not modifiable function {0}. \
                    Must be contained in module {1}".format(target, module)
                )

    elif ctx is None:
        # update code with interception code
        target_fn = _get_function(target)
        # switch interception and intercepted
        interception, intercepted = target, interception_fn
        # switch of code between target_fn and
        # interception_fn
        target_fn.__code__, interception_fn.__code__ = \
            interception_fn.__code__, target_fn.__code__

    else:
        # get target name
        if isclass(target):  # if target is a class, get constructor name
            target_name = _get_function(target).__name__
        else:  # else get target name
            target_name = target.__name__
        # get the right intercepted
        intercepted = getattr(ctx, target_name)
        # in case of method
        if ismethod(intercepted):  # in creating eventually a new method
            args = [interception, ctx]
            if PY2:  # if py2, specify the ctx class
                # and unbound method type
                if intercepted.__self__ is None:
                    args = [interception, None, ctx]
                else:
                    args.append(ctx.__class__)
            # instantiate a new method
            interception = MethodType(*args)
        # get the right intercepted function
        if is_intercepted(intercepted):
            intercepted, _ = get_intercepted(intercepted)
        else:
            intercepted = _get_function(intercepted)
        # set in ctx the new method
        setattr(ctx, target_name, interception)

    # add intercepted into interception_fn globals and attributes
    interception_fn = _get_function(interception)
    # set intercepted
    setattr(interception_fn, _INTERCEPTED, intercepted)
    # set intercepted ctx
    if ctx is not None:
        setattr(interception_fn, _INTERCEPTED_CTX, ctx)

    interception_fn.__globals__[_INTERCEPTED] = intercepted
    interception_fn.__globals__[_INTERCEPTION] = interception

    if _globals is not None:
        interception_fn.__globals__.update(_globals)

    return interception, intercepted, ctx