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