Coverage for src/c41811/config/lazy_import.py: 100%
37 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-04 11:16 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-04 11:16 +0000
1# cython: language_level = 3 # noqa: ERA001
4"""
5懒加载处理
7.. versionadded:: 0.3.0
8"""
10import inspect
11from importlib import import_module
12from typing import TYPE_CHECKING
13from typing import Any
15if TYPE_CHECKING:
16 from collections.abc import Callable
17else:
18 from collections.abc import Callable
21def lazy_import(properties: dict[str, str], /) -> tuple[list[str], Callable[[str], Any]]:
22 """
23 为 `__init__` 文件生成 `__all__` 和 `__getattr__`
25 :param properties: 属性字典 ``dict[属性, 模块]``
26 :type properties: dict[str, str]
28 :return: 返回 ``tuple[__all__, __getattr__]``
29 :rtype: tuple[tuple[str, ...], Callable[[str], Any]]
31 .. versionadded:: 0.3.0
32 """
33 if (caller_module := inspect.getmodule(inspect.stack()[1][0])) is None: # pragma: no cover
34 msg = "Cannot find caller module"
35 raise RuntimeError(msg)
36 caller_package = caller_module.__name__
37 property_list = list(properties.keys())
38 unavailable_properties: dict[str, Exception] = {}
40 def attr_getter(name: str) -> Any:
41 from .errors import DependencyNotFoundError # noqa: PLC0415
42 from .errors import UnavailableAttribute # noqa: PLC0415
44 try:
45 sub_pkg = properties[name]
46 except KeyError:
47 if name in unavailable_properties:
48 raise unavailable_properties[name] from None
49 # noinspection PyShadowingNames
50 msg = f"module '{caller_package}' has no attribute '{name}'"
51 raise AttributeError(msg) from None
52 try:
53 module = import_module(sub_pkg, package=caller_package)
54 except DependencyNotFoundError as err:
55 property_list.remove(name)
56 del properties[name]
57 unavailable_properties[name] = err
58 return UnavailableAttribute(name, err)
59 attr = getattr(module, name)
60 if isinstance(attr, UnavailableAttribute):
61 property_list.remove(name)
62 del properties[name]
63 # 绕过UnavailableAttribute复写的__getattribute__防止异常被提前抛出
64 unavailable_properties[name] = object.__getattribute__(attr, "_reason")
65 return attr
67 attr_getter.__name__ = "__getattr__"
68 attr_getter.__qualname__ = f"{caller_package}.__getattr__"
69 attr_getter.__module__ = caller_package
71 return property_list, attr_getter
74__all__ = ("lazy_import",)