"""
dynamic import functionality
"""

__all__ = ("FailedImport", "load_module", "load_attribute", "load_any")

import sys
from importlib import import_module

from .compatibility import IGNORED_EXCEPTIONS


class FailedImport(ImportError):
    """
    Raised when a requested target cannot be imported
    """

    def __init__(self, trg, e):
        super().__init__(self, f"Failed importing target '{trg}': '{e}'")
        self.trg, self.e = trg, e


def load_module(name):
    """load a module

    Deprecated, use ``importlib.import_module`` instead.

    :param name: python dotted namespace path of the module to import
    :raise: FailedImport if importing fails
    :return: imported module
    """
    if name in sys.modules:
        return sys.modules[name]
    try:
        return import_module(name)
    except IGNORED_EXCEPTIONS:
        raise
    except Exception as exc:
        raise FailedImport(name, exc) from exc


def load_attribute(name):
    """load an attribute from a module

    :param name: python dotted namespace path of the attribute to load from a
        module for example, ``snakeoil.modules.load_module`` would return
        :py:func:`load_module`
    :raise: FailedImport if importing fails, or the requested attribute cannot
        be found
    :return: attribute resolved from `name`
    """
    chunks = name.rsplit(".", 1)
    if len(chunks) == 1:
        raise FailedImport(name, "it isn't an attribute, it's a module")
    try:
        return getattr(import_module(chunks[0]), chunks[1])
    except (AttributeError, ImportError) as exc:
        # try to show actual import error if it exists
        try:
            import_module(name)
        except ImportError as exc:
            raise FailedImport(name, exc) from exc
        raise FailedImport(name, exc) from exc


def load_any(name):
    """load an attribute or a module from a namespace

    :param name: python dotted namespace path of the object to load from a
        module for example, ``snakeoil.modules.load_module`` would return
        :py:func:`load_module`, and ``snakeoil.modules`` would return `modules`
    :raise: FailedImport if importing fails, or the requested attribute cannot
        be found
    :return: object resolved from `name`
    """

    try:
        return import_module(name)
    except ImportError:
        return load_attribute(name)
    except Exception as exc:
        raise FailedImport(name, exc) from exc
