1import importlib
2import typing
3
4
5class ImportFromStringError(Exception):
6    pass
7
8
9def import_from_string(import_str: str) -> typing.Any:
10    module_str, _, attrs_str = import_str.partition(":")
11    if not module_str or not attrs_str:
12        message = (
13            'Import string "{import_str}" must be in format "<module>:<attribute>".'
14        )
15        raise ImportFromStringError(message.format(import_str=import_str))
16
17    try:
18        module = importlib.import_module(module_str)
19    except ImportError as exc:
20        if exc.name != module_str:
21            raise exc from None
22        message = 'Could not import module "{module_str}".'
23        raise ImportFromStringError(message.format(module_str=module_str))
24
25    instance = module
26    try:
27        for attr_str in attrs_str.split("."):
28            instance = getattr(instance, attr_str)
29    except AttributeError as exc:
30        message = 'Attribute "{attrs_str}" not found in module "{module_str}".'
31        raise ImportFromStringError(
32            message.format(attrs_str=attrs_str, module_str=module_str)
33        )
34
35    return instance
36