Coverage for src/lazy_imports_lite/_loader.py: 100%

68 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-31 08:58 +0100

1import ast 

2import importlib.abc 

3import importlib.machinery 

4import importlib.metadata 

5import os 

6import sys 

7import types 

8from functools import lru_cache 

9 

10from ._hooks import LazyObject 

11from ._transformer import TransformModuleImports 

12 

13 

14class LazyModule(types.ModuleType): 

15 def __getattribute__(self, name): 

16 v = super().__getattribute__(name) 

17 if isinstance(v, LazyObject): 

18 return v.v 

19 return v 

20 

21 def __setattr__(self, name, value): 

22 try: 

23 v = super().__getattribute__(name) 

24 except: 

25 super().__setattr__(name, value) 

26 else: 

27 if isinstance(v, LazyObject): 

28 v.v = value 

29 else: 

30 super().__setattr__(name, value) 

31 

32 

33@lru_cache 

34def is_enabled_by_metadata(name): 

35 if name != "lazy_imports_lite": 

36 try: 

37 metadata = importlib.metadata.metadata(name) 

38 except importlib.metadata.PackageNotFoundError: 

39 return False 

40 

41 if metadata is None: 

42 return False # pragma: no cover 

43 

44 if metadata["Keywords"] is None: 

45 return False 

46 

47 keywords = metadata["Keywords"].split(",") 

48 if "lazy-imports-lite-enabled" in keywords: 

49 return True 

50 

51 return False 

52 

53 

54class LazyLoader(importlib.abc.Loader, importlib.machinery.PathFinder): 

55 def find_spec(self, fullname, path=None, target=None): 

56 if "LAZY_IMPORTS_LITE_DISABLE" in os.environ: 

57 return None 

58 

59 spec = super().find_spec(fullname, path, target) 

60 

61 if spec is None: 

62 return None 

63 

64 if spec.origin is None: 

65 return None # pragma: no cover 

66 

67 name = spec.name.split(".")[0] 

68 

69 if is_enabled_by_metadata(name) and spec.origin.endswith(".py"): 

70 spec.loader = self 

71 return spec 

72 

73 return None 

74 

75 def create_module(self, spec): 

76 return LazyModule(spec.name) 

77 

78 def exec_module(self, module): 

79 origin: str = module.__spec__.origin 

80 with open(origin) as f: 

81 mod_raw = f.read() 

82 mod_ast = ast.parse(mod_raw, origin, "exec") 

83 transformer = TransformModuleImports() 

84 new_ast = transformer.visit(mod_ast) 

85 

86 ast.fix_missing_locations(new_ast) 

87 mod_code = compile(new_ast, origin, "exec") 

88 exec(mod_code, module.__dict__) 

89 del module.__dict__["__lazy_imports_lite__"] 

90 del module.__dict__["globals"] 

91 

92 

93def setup(): 

94 if not any(isinstance(m, LazyLoader) for m in sys.meta_path): 

95 sys.meta_path.insert(0, LazyLoader())