This function inspects a function to find its arguments marked for
    injection, either with :py:func:`inject()` decorator,
    :py:class:`UnrealizedInjection` class of through Python 3 function
    annotations.  If `factory` is a class, then its constructor is inspected.

    Returned dictionary is a mapping of::

        [argument index/name] -> [specification]

    For example, dependencies for function::

        @inject(ILogger, db=(IDBConnection, 'archive'))
        def foo(log, db=None):
            pass

    would be::

        {
            0: ILogger,
            'db': (IDBConnection, 'archive'),
        }

    `Old-style classes`_ (from before Python 2.2) are not supported.

    .. _Old-style classes:
        https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes

def get_dependencies(factory):
    """
    This function inspects a function to find its arguments marked for
    injection, either with :py:func:`inject()` decorator,
    :py:class:`UnrealizedInjection` class of through Python 3 function
    annotations.  If `factory` is a class, then its constructor is inspected.

    Returned dictionary is a mapping of::

        [argument index/name] -> [specification]

    For example, dependencies for function::

        @inject(ILogger, db=(IDBConnection, 'archive'))
        def foo(log, db=None):
            pass

    would be::

        {
            0: ILogger,
            'db': (IDBConnection, 'archive'),
        }

    `Old-style classes`_ (from before Python 2.2) are not supported.

    .. _Old-style classes:
        https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes
    """
    if inspect.isclass(factory):
        # If factory is a class we want to check constructor depdendencies.
        if six.PY3:
            init_check = inspect.isfunction
        else:
            init_check = inspect.ismethod
        dependencies = {}
        if hasattr(factory, '__init__') and init_check(factory.__init__):
            dependencies.update(get_dependencies(factory.__init__))
        if hasattr(factory, '__new__') and inspect.isfunction(factory.__new__):
            dependencies.update(get_dependencies(factory.__new__))
        return dependencies
    elif inspect.isfunction(factory) or inspect.ismethod(factory):
        function = factory
    else:
        raise TypeError("`factory` must be a class or a function.")

    if hasattr(function, '__injection__'):
        # Function has precollected dependencies (happens when using `inject()`
        # decorator. Nothing to do here.
        return function.__injection__

    dependencies = {}

    def process_dependency_tuples(tuples):
        for key, value in tuples:
            if isinstance(value, UnrealizedInjection):
                dependencies[key] = value.specification
    if six.PY3:
        argument_specification = inspect.getfullargspec(function)
        if argument_specification.kwonlydefaults:
            process_dependency_tuples(
                six.iteritems(argument_specification.kwonlydefaults)
            )
        if argument_specification.annotations:
            dependencies.update(argument_specification.annotations)
    else:
        argument_specification = inspect.getargspec(function)
    if argument_specification.defaults:
        process_dependency_tuples(zip(
            reversed(argument_specification.args),
            reversed(argument_specification.defaults)
        ))
    return dependencies