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

1# cython: language_level = 3 # noqa: ERA001 

2 

3 

4""" 

5懒加载处理 

6 

7.. versionadded:: 0.3.0 

8""" 

9 

10import inspect 

11from importlib import import_module 

12from typing import TYPE_CHECKING 

13from typing import Any 

14 

15if TYPE_CHECKING: 

16 from collections.abc import Callable 

17else: 

18 from collections.abc import Callable 

19 

20 

21def lazy_import(properties: dict[str, str], /) -> tuple[list[str], Callable[[str], Any]]: 

22 """ 

23 为 `__init__` 文件生成 `__all__` 和 `__getattr__` 

24 

25 :param properties: 属性字典 ``dict[属性, 模块]`` 

26 :type properties: dict[str, str] 

27 

28 :return: 返回 ``tuple[__all__, __getattr__]`` 

29 :rtype: tuple[tuple[str, ...], Callable[[str], Any]] 

30 

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] = {} 

39 

40 def attr_getter(name: str) -> Any: 

41 from .errors import DependencyNotFoundError # noqa: PLC0415 

42 from .errors import UnavailableAttribute # noqa: PLC0415 

43 

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 

66 

67 attr_getter.__name__ = "__getattr__" 

68 attr_getter.__qualname__ = f"{caller_package}.__getattr__" 

69 attr_getter.__module__ = caller_package 

70 

71 return property_list, attr_getter 

72 

73 

74__all__ = ("lazy_import",)