1"""Utility code for constructing importers, etc.""" 2from ._abc import Loader 3from ._bootstrap import module_from_spec 4from ._bootstrap import _resolve_name 5from ._bootstrap import spec_from_loader 6from ._bootstrap import _find_spec 7from ._bootstrap_external import MAGIC_NUMBER 8from ._bootstrap_external import _RAW_MAGIC_NUMBER 9from ._bootstrap_external import cache_from_source 10from ._bootstrap_external import decode_source 11from ._bootstrap_external import source_from_cache 12from ._bootstrap_external import spec_from_file_location 13 14from contextlib import contextmanager 15import _imp 16import functools 17import sys 18import types 19import warnings 20 21 22def source_hash(source_bytes): 23 "Return the hash of *source_bytes* as used in hash-based pyc files." 24 return _imp.source_hash(_RAW_MAGIC_NUMBER, source_bytes) 25 26 27def resolve_name(name, package): 28 """Resolve a relative module name to an absolute one.""" 29 if not name.startswith('.'): 30 return name 31 elif not package: 32 raise ImportError(f'no package specified for {repr(name)} ' 33 '(required for relative module names)') 34 level = 0 35 for character in name: 36 if character != '.': 37 break 38 level += 1 39 return _resolve_name(name[level:], package, level) 40 41 42def _find_spec_from_path(name, path=None): 43 """Return the spec for the specified module. 44 45 First, sys.modules is checked to see if the module was already imported. If 46 so, then sys.modules[name].__spec__ is returned. If that happens to be 47 set to None, then ValueError is raised. If the module is not in 48 sys.modules, then sys.meta_path is searched for a suitable spec with the 49 value of 'path' given to the finders. None is returned if no spec could 50 be found. 51 52 Dotted names do not have their parent packages implicitly imported. You will 53 most likely need to explicitly import all parent packages in the proper 54 order for a submodule to get the correct spec. 55 56 """ 57 if name not in sys.modules: 58 return _find_spec(name, path) 59 else: 60 module = sys.modules[name] 61 if module is None: 62 return None 63 try: 64 spec = module.__spec__ 65 except AttributeError: 66 raise ValueError('{}.__spec__ is not set'.format(name)) from None 67 else: 68 if spec is None: 69 raise ValueError('{}.__spec__ is None'.format(name)) 70 return spec 71 72 73def find_spec(name, package=None): 74 """Return the spec for the specified module. 75 76 First, sys.modules is checked to see if the module was already imported. If 77 so, then sys.modules[name].__spec__ is returned. If that happens to be 78 set to None, then ValueError is raised. If the module is not in 79 sys.modules, then sys.meta_path is searched for a suitable spec with the 80 value of 'path' given to the finders. None is returned if no spec could 81 be found. 82 83 If the name is for submodule (contains a dot), the parent module is 84 automatically imported. 85 86 The name and package arguments work the same as importlib.import_module(). 87 In other words, relative module names (with leading dots) work. 88 89 """ 90 fullname = resolve_name(name, package) if name.startswith('.') else name 91 if fullname not in sys.modules: 92 parent_name = fullname.rpartition('.')[0] 93 if parent_name: 94 parent = __import__(parent_name, fromlist=['__path__']) 95 try: 96 parent_path = parent.__path__ 97 except AttributeError as e: 98 raise ModuleNotFoundError( 99 f"__path__ attribute not found on {parent_name!r} " 100 f"while trying to find {fullname!r}", name=fullname) from e 101 else: 102 parent_path = None 103 return _find_spec(fullname, parent_path) 104 else: 105 module = sys.modules[fullname] 106 if module is None: 107 return None 108 try: 109 spec = module.__spec__ 110 except AttributeError: 111 raise ValueError('{}.__spec__ is not set'.format(name)) from None 112 else: 113 if spec is None: 114 raise ValueError('{}.__spec__ is None'.format(name)) 115 return spec 116 117 118@contextmanager 119def _module_to_load(name): 120 is_reload = name in sys.modules 121 122 module = sys.modules.get(name) 123 if not is_reload: 124 # This must be done before open() is called as the 'io' module 125 # implicitly imports 'locale' and would otherwise trigger an 126 # infinite loop. 127 module = type(sys)(name) 128 # This must be done before putting the module in sys.modules 129 # (otherwise an optimization shortcut in import.c becomes wrong) 130 module.__initializing__ = True 131 sys.modules[name] = module 132 try: 133 yield module 134 except Exception: 135 if not is_reload: 136 try: 137 del sys.modules[name] 138 except KeyError: 139 pass 140 finally: 141 module.__initializing__ = False 142 143 144def set_package(fxn): 145 """Set __package__ on the returned module. 146 147 This function is deprecated. 148 149 """ 150 @functools.wraps(fxn) 151 def set_package_wrapper(*args, **kwargs): 152 warnings.warn('The import system now takes care of this automatically; ' 153 'this decorator is slated for removal in Python 3.12', 154 DeprecationWarning, stacklevel=2) 155 module = fxn(*args, **kwargs) 156 if getattr(module, '__package__', None) is None: 157 module.__package__ = module.__name__ 158 if not hasattr(module, '__path__'): 159 module.__package__ = module.__package__.rpartition('.')[0] 160 return module 161 return set_package_wrapper 162 163 164def set_loader(fxn): 165 """Set __loader__ on the returned module. 166 167 This function is deprecated. 168 169 """ 170 @functools.wraps(fxn) 171 def set_loader_wrapper(self, *args, **kwargs): 172 warnings.warn('The import system now takes care of this automatically; ' 173 'this decorator is slated for removal in Python 3.12', 174 DeprecationWarning, stacklevel=2) 175 module = fxn(self, *args, **kwargs) 176 if getattr(module, '__loader__', None) is None: 177 module.__loader__ = self 178 return module 179 return set_loader_wrapper 180 181 182def module_for_loader(fxn): 183 """Decorator to handle selecting the proper module for loaders. 184 185 The decorated function is passed the module to use instead of the module 186 name. The module passed in to the function is either from sys.modules if 187 it already exists or is a new module. If the module is new, then __name__ 188 is set the first argument to the method, __loader__ is set to self, and 189 __package__ is set accordingly (if self.is_package() is defined) will be set 190 before it is passed to the decorated function (if self.is_package() does 191 not work for the module it will be set post-load). 192 193 If an exception is raised and the decorator created the module it is 194 subsequently removed from sys.modules. 195 196 The decorator assumes that the decorated function takes the module name as 197 the second argument. 198 199 """ 200 warnings.warn('The import system now takes care of this automatically; ' 201 'this decorator is slated for removal in Python 3.12', 202 DeprecationWarning, stacklevel=2) 203 @functools.wraps(fxn) 204 def module_for_loader_wrapper(self, fullname, *args, **kwargs): 205 with _module_to_load(fullname) as module: 206 module.__loader__ = self 207 try: 208 is_package = self.is_package(fullname) 209 except (ImportError, AttributeError): 210 pass 211 else: 212 if is_package: 213 module.__package__ = fullname 214 else: 215 module.__package__ = fullname.rpartition('.')[0] 216 # If __package__ was not set above, __import__() will do it later. 217 return fxn(self, module, *args, **kwargs) 218 219 return module_for_loader_wrapper 220 221 222class _LazyModule(types.ModuleType): 223 224 """A subclass of the module type which triggers loading upon attribute access.""" 225 226 def __getattribute__(self, attr): 227 """Trigger the load of the module and return the attribute.""" 228 # All module metadata must be garnered from __spec__ in order to avoid 229 # using mutated values. 230 # Stop triggering this method. 231 self.__class__ = types.ModuleType 232 # Get the original name to make sure no object substitution occurred 233 # in sys.modules. 234 original_name = self.__spec__.name 235 # Figure out exactly what attributes were mutated between the creation 236 # of the module and now. 237 attrs_then = self.__spec__.loader_state['__dict__'] 238 attrs_now = self.__dict__ 239 attrs_updated = {} 240 for key, value in attrs_now.items(): 241 # Code that set the attribute may have kept a reference to the 242 # assigned object, making identity more important than equality. 243 if key not in attrs_then: 244 attrs_updated[key] = value 245 elif id(attrs_now[key]) != id(attrs_then[key]): 246 attrs_updated[key] = value 247 self.__spec__.loader.exec_module(self) 248 # If exec_module() was used directly there is no guarantee the module 249 # object was put into sys.modules. 250 if original_name in sys.modules: 251 if id(self) != id(sys.modules[original_name]): 252 raise ValueError(f"module object for {original_name!r} " 253 "substituted in sys.modules during a lazy " 254 "load") 255 # Update after loading since that's what would happen in an eager 256 # loading situation. 257 self.__dict__.update(attrs_updated) 258 return getattr(self, attr) 259 260 def __delattr__(self, attr): 261 """Trigger the load and then perform the deletion.""" 262 # To trigger the load and raise an exception if the attribute 263 # doesn't exist. 264 self.__getattribute__(attr) 265 delattr(self, attr) 266 267 268class LazyLoader(Loader): 269 270 """A loader that creates a module which defers loading until attribute access.""" 271 272 @staticmethod 273 def __check_eager_loader(loader): 274 if not hasattr(loader, 'exec_module'): 275 raise TypeError('loader must define exec_module()') 276 277 @classmethod 278 def factory(cls, loader): 279 """Construct a callable which returns the eager loader made lazy.""" 280 cls.__check_eager_loader(loader) 281 return lambda *args, **kwargs: cls(loader(*args, **kwargs)) 282 283 def __init__(self, loader): 284 self.__check_eager_loader(loader) 285 self.loader = loader 286 287 def create_module(self, spec): 288 return self.loader.create_module(spec) 289 290 def exec_module(self, module): 291 """Make the module load lazily.""" 292 module.__spec__.loader = self.loader 293 module.__loader__ = self.loader 294 # Don't need to worry about deep-copying as trying to set an attribute 295 # on an object would have triggered the load, 296 # e.g. ``module.__spec__.loader = None`` would trigger a load from 297 # trying to access module.__spec__. 298 loader_state = {} 299 loader_state['__dict__'] = module.__dict__.copy() 300 loader_state['__class__'] = module.__class__ 301 module.__spec__.loader_state = loader_state 302 module.__class__ = _LazyModule 303