v0.150.0
 1from __future__ import annotations
 2
 3import os
 4import sys
 5from importlib import import_module
 6from types import ModuleType
 7from typing import Any
 8
 9
10def cached_import(module_path: str, class_name: str) -> Any:
11    # Check whether module is loaded and fully initialized.
12    if not (
13        (module := sys.modules.get(module_path))
14        and (spec := getattr(module, "__spec__", None))
15        and getattr(spec, "_initializing", False) is False
16    ):
17        module = import_module(module_path)
18    return getattr(module, class_name)
19
20
21def import_string(dotted_path: str) -> Any:
22    """
23    Import a dotted module path and return the attribute/class designated by the
24    last name in the path. Raise ImportError if the import failed.
25    """
26    try:
27        module_path, class_name = dotted_path.rsplit(".", 1)
28    except ValueError as err:
29        raise ImportError(f"{dotted_path} doesn't look like a module path") from err
30
31    try:
32        return cached_import(module_path, class_name)
33    except AttributeError as err:
34        raise ImportError(
35            f'Module "{module_path}" does not define a "{class_name}" attribute/class'
36        ) from err
37
38
39def module_dir(module: ModuleType) -> str:
40    """
41    Find the name of the directory that contains a module, if possible.
42
43    Raise ValueError otherwise, e.g. for namespace packages that are split
44    over several directories.
45    """
46    # Convert to list because __path__ may not support indexing.
47    paths = list(getattr(module, "__path__", []))
48    if len(paths) == 1:
49        return paths[0]
50    else:
51        filename = getattr(module, "__file__", None)
52        if filename is not None:
53            return os.path.dirname(filename)
54    raise ValueError(f"Cannot determine directory containing {module}")