import sys
import traceback

# Python 3 compatibility
from ._compat import reduce


def import_module(name, required=True):
    """
        Import module by name

        :param name:
            Module name
        :param required:
            If set to `True` and module was not found - will throw exception.
            If set to `False` and module was not found - will return None.
            Default is `True`.
    """
    try:
        __import__(name, globals(), locals(), [])
    except ImportError:
        if not required and module_not_found():
            return None
        raise
    return sys.modules[name]


def import_attribute(name):
    """
        Import attribute using string reference.

        :param name:
            String reference.

        Raises ImportError or AttributeError if module or attribute do not exist.

        Example::

            import_attribute('a.b.c.foo')

    """
    path, attr = name.rsplit('.', 1)
    module = __import__(path, globals(), locals(), [attr])

    return getattr(module, attr)


def module_not_found(additional_depth=0):
    """
        Checks if ImportError was raised because module does not exist or
        something inside it raised ImportError

        :param additional_depth:
            supply int of depth of your call if you're not doing
            import on the same level of code - f.e., if you call function, which is
            doing import, you should pass 1 for single additional level of depth
    """
    tb = sys.exc_info()[2]
    if len(traceback.extract_tb(tb)) > (1 + additional_depth):
        return False
    return True


def rec_getattr(obj, attr, default=None):
    """
        Recursive getattr.

        :param attr:
            Dot delimited attribute name
        :param default:
            Default value

        Example::

            rec_getattr(obj, 'a.b.c')
    """
    try:
        return reduce(getattr, attr.split('.'), obj)
    except AttributeError:
        return default


def get_dict_attr(obj, attr, default=None):
    """
        Get attribute of the object without triggering its __getattr__.

        :param obj:
            Object
        :param attr:
            Attribute name
        :param default:
            Default value if attribute was not found
    """
    for obj in [obj] + obj.__class__.mro():
        if attr in obj.__dict__:
            return obj.__dict__[attr]

    return default
