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
« 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
10from ._hooks import LazyObject
11from ._transformer import TransformModuleImports
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
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)
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
41 if metadata is None:
42 return False # pragma: no cover
44 if metadata["Keywords"] is None:
45 return False
47 keywords = metadata["Keywords"].split(",")
48 if "lazy-imports-lite-enabled" in keywords:
49 return True
51 return False
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
59 spec = super().find_spec(fullname, path, target)
61 if spec is None:
62 return None
64 if spec.origin is None:
65 return None # pragma: no cover
67 name = spec.name.split(".")[0]
69 if is_enabled_by_metadata(name) and spec.origin.endswith(".py"):
70 spec.loader = self
71 return spec
73 return None
75 def create_module(self, spec):
76 return LazyModule(spec.name)
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)
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"]
93def setup():
94 if not any(isinstance(m, LazyLoader) for m in sys.meta_path):
95 sys.meta_path.insert(0, LazyLoader())