17db96d56Sopenharmony_ci"""Utility code for constructing importers, etc.""" 27db96d56Sopenharmony_cifrom ._abc import Loader 37db96d56Sopenharmony_cifrom ._bootstrap import module_from_spec 47db96d56Sopenharmony_cifrom ._bootstrap import _resolve_name 57db96d56Sopenharmony_cifrom ._bootstrap import spec_from_loader 67db96d56Sopenharmony_cifrom ._bootstrap import _find_spec 77db96d56Sopenharmony_cifrom ._bootstrap_external import MAGIC_NUMBER 87db96d56Sopenharmony_cifrom ._bootstrap_external import _RAW_MAGIC_NUMBER 97db96d56Sopenharmony_cifrom ._bootstrap_external import cache_from_source 107db96d56Sopenharmony_cifrom ._bootstrap_external import decode_source 117db96d56Sopenharmony_cifrom ._bootstrap_external import source_from_cache 127db96d56Sopenharmony_cifrom ._bootstrap_external import spec_from_file_location 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_cifrom contextlib import contextmanager 157db96d56Sopenharmony_ciimport _imp 167db96d56Sopenharmony_ciimport functools 177db96d56Sopenharmony_ciimport sys 187db96d56Sopenharmony_ciimport types 197db96d56Sopenharmony_ciimport warnings 207db96d56Sopenharmony_ci 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_cidef source_hash(source_bytes): 237db96d56Sopenharmony_ci "Return the hash of *source_bytes* as used in hash-based pyc files." 247db96d56Sopenharmony_ci return _imp.source_hash(_RAW_MAGIC_NUMBER, source_bytes) 257db96d56Sopenharmony_ci 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_cidef resolve_name(name, package): 287db96d56Sopenharmony_ci """Resolve a relative module name to an absolute one.""" 297db96d56Sopenharmony_ci if not name.startswith('.'): 307db96d56Sopenharmony_ci return name 317db96d56Sopenharmony_ci elif not package: 327db96d56Sopenharmony_ci raise ImportError(f'no package specified for {repr(name)} ' 337db96d56Sopenharmony_ci '(required for relative module names)') 347db96d56Sopenharmony_ci level = 0 357db96d56Sopenharmony_ci for character in name: 367db96d56Sopenharmony_ci if character != '.': 377db96d56Sopenharmony_ci break 387db96d56Sopenharmony_ci level += 1 397db96d56Sopenharmony_ci return _resolve_name(name[level:], package, level) 407db96d56Sopenharmony_ci 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_cidef _find_spec_from_path(name, path=None): 437db96d56Sopenharmony_ci """Return the spec for the specified module. 447db96d56Sopenharmony_ci 457db96d56Sopenharmony_ci First, sys.modules is checked to see if the module was already imported. If 467db96d56Sopenharmony_ci so, then sys.modules[name].__spec__ is returned. If that happens to be 477db96d56Sopenharmony_ci set to None, then ValueError is raised. If the module is not in 487db96d56Sopenharmony_ci sys.modules, then sys.meta_path is searched for a suitable spec with the 497db96d56Sopenharmony_ci value of 'path' given to the finders. None is returned if no spec could 507db96d56Sopenharmony_ci be found. 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci Dotted names do not have their parent packages implicitly imported. You will 537db96d56Sopenharmony_ci most likely need to explicitly import all parent packages in the proper 547db96d56Sopenharmony_ci order for a submodule to get the correct spec. 557db96d56Sopenharmony_ci 567db96d56Sopenharmony_ci """ 577db96d56Sopenharmony_ci if name not in sys.modules: 587db96d56Sopenharmony_ci return _find_spec(name, path) 597db96d56Sopenharmony_ci else: 607db96d56Sopenharmony_ci module = sys.modules[name] 617db96d56Sopenharmony_ci if module is None: 627db96d56Sopenharmony_ci return None 637db96d56Sopenharmony_ci try: 647db96d56Sopenharmony_ci spec = module.__spec__ 657db96d56Sopenharmony_ci except AttributeError: 667db96d56Sopenharmony_ci raise ValueError('{}.__spec__ is not set'.format(name)) from None 677db96d56Sopenharmony_ci else: 687db96d56Sopenharmony_ci if spec is None: 697db96d56Sopenharmony_ci raise ValueError('{}.__spec__ is None'.format(name)) 707db96d56Sopenharmony_ci return spec 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ci 737db96d56Sopenharmony_cidef find_spec(name, package=None): 747db96d56Sopenharmony_ci """Return the spec for the specified module. 757db96d56Sopenharmony_ci 767db96d56Sopenharmony_ci First, sys.modules is checked to see if the module was already imported. If 777db96d56Sopenharmony_ci so, then sys.modules[name].__spec__ is returned. If that happens to be 787db96d56Sopenharmony_ci set to None, then ValueError is raised. If the module is not in 797db96d56Sopenharmony_ci sys.modules, then sys.meta_path is searched for a suitable spec with the 807db96d56Sopenharmony_ci value of 'path' given to the finders. None is returned if no spec could 817db96d56Sopenharmony_ci be found. 827db96d56Sopenharmony_ci 837db96d56Sopenharmony_ci If the name is for submodule (contains a dot), the parent module is 847db96d56Sopenharmony_ci automatically imported. 857db96d56Sopenharmony_ci 867db96d56Sopenharmony_ci The name and package arguments work the same as importlib.import_module(). 877db96d56Sopenharmony_ci In other words, relative module names (with leading dots) work. 887db96d56Sopenharmony_ci 897db96d56Sopenharmony_ci """ 907db96d56Sopenharmony_ci fullname = resolve_name(name, package) if name.startswith('.') else name 917db96d56Sopenharmony_ci if fullname not in sys.modules: 927db96d56Sopenharmony_ci parent_name = fullname.rpartition('.')[0] 937db96d56Sopenharmony_ci if parent_name: 947db96d56Sopenharmony_ci parent = __import__(parent_name, fromlist=['__path__']) 957db96d56Sopenharmony_ci try: 967db96d56Sopenharmony_ci parent_path = parent.__path__ 977db96d56Sopenharmony_ci except AttributeError as e: 987db96d56Sopenharmony_ci raise ModuleNotFoundError( 997db96d56Sopenharmony_ci f"__path__ attribute not found on {parent_name!r} " 1007db96d56Sopenharmony_ci f"while trying to find {fullname!r}", name=fullname) from e 1017db96d56Sopenharmony_ci else: 1027db96d56Sopenharmony_ci parent_path = None 1037db96d56Sopenharmony_ci return _find_spec(fullname, parent_path) 1047db96d56Sopenharmony_ci else: 1057db96d56Sopenharmony_ci module = sys.modules[fullname] 1067db96d56Sopenharmony_ci if module is None: 1077db96d56Sopenharmony_ci return None 1087db96d56Sopenharmony_ci try: 1097db96d56Sopenharmony_ci spec = module.__spec__ 1107db96d56Sopenharmony_ci except AttributeError: 1117db96d56Sopenharmony_ci raise ValueError('{}.__spec__ is not set'.format(name)) from None 1127db96d56Sopenharmony_ci else: 1137db96d56Sopenharmony_ci if spec is None: 1147db96d56Sopenharmony_ci raise ValueError('{}.__spec__ is None'.format(name)) 1157db96d56Sopenharmony_ci return spec 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci@contextmanager 1197db96d56Sopenharmony_cidef _module_to_load(name): 1207db96d56Sopenharmony_ci is_reload = name in sys.modules 1217db96d56Sopenharmony_ci 1227db96d56Sopenharmony_ci module = sys.modules.get(name) 1237db96d56Sopenharmony_ci if not is_reload: 1247db96d56Sopenharmony_ci # This must be done before open() is called as the 'io' module 1257db96d56Sopenharmony_ci # implicitly imports 'locale' and would otherwise trigger an 1267db96d56Sopenharmony_ci # infinite loop. 1277db96d56Sopenharmony_ci module = type(sys)(name) 1287db96d56Sopenharmony_ci # This must be done before putting the module in sys.modules 1297db96d56Sopenharmony_ci # (otherwise an optimization shortcut in import.c becomes wrong) 1307db96d56Sopenharmony_ci module.__initializing__ = True 1317db96d56Sopenharmony_ci sys.modules[name] = module 1327db96d56Sopenharmony_ci try: 1337db96d56Sopenharmony_ci yield module 1347db96d56Sopenharmony_ci except Exception: 1357db96d56Sopenharmony_ci if not is_reload: 1367db96d56Sopenharmony_ci try: 1377db96d56Sopenharmony_ci del sys.modules[name] 1387db96d56Sopenharmony_ci except KeyError: 1397db96d56Sopenharmony_ci pass 1407db96d56Sopenharmony_ci finally: 1417db96d56Sopenharmony_ci module.__initializing__ = False 1427db96d56Sopenharmony_ci 1437db96d56Sopenharmony_ci 1447db96d56Sopenharmony_cidef set_package(fxn): 1457db96d56Sopenharmony_ci """Set __package__ on the returned module. 1467db96d56Sopenharmony_ci 1477db96d56Sopenharmony_ci This function is deprecated. 1487db96d56Sopenharmony_ci 1497db96d56Sopenharmony_ci """ 1507db96d56Sopenharmony_ci @functools.wraps(fxn) 1517db96d56Sopenharmony_ci def set_package_wrapper(*args, **kwargs): 1527db96d56Sopenharmony_ci warnings.warn('The import system now takes care of this automatically; ' 1537db96d56Sopenharmony_ci 'this decorator is slated for removal in Python 3.12', 1547db96d56Sopenharmony_ci DeprecationWarning, stacklevel=2) 1557db96d56Sopenharmony_ci module = fxn(*args, **kwargs) 1567db96d56Sopenharmony_ci if getattr(module, '__package__', None) is None: 1577db96d56Sopenharmony_ci module.__package__ = module.__name__ 1587db96d56Sopenharmony_ci if not hasattr(module, '__path__'): 1597db96d56Sopenharmony_ci module.__package__ = module.__package__.rpartition('.')[0] 1607db96d56Sopenharmony_ci return module 1617db96d56Sopenharmony_ci return set_package_wrapper 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci 1647db96d56Sopenharmony_cidef set_loader(fxn): 1657db96d56Sopenharmony_ci """Set __loader__ on the returned module. 1667db96d56Sopenharmony_ci 1677db96d56Sopenharmony_ci This function is deprecated. 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci """ 1707db96d56Sopenharmony_ci @functools.wraps(fxn) 1717db96d56Sopenharmony_ci def set_loader_wrapper(self, *args, **kwargs): 1727db96d56Sopenharmony_ci warnings.warn('The import system now takes care of this automatically; ' 1737db96d56Sopenharmony_ci 'this decorator is slated for removal in Python 3.12', 1747db96d56Sopenharmony_ci DeprecationWarning, stacklevel=2) 1757db96d56Sopenharmony_ci module = fxn(self, *args, **kwargs) 1767db96d56Sopenharmony_ci if getattr(module, '__loader__', None) is None: 1777db96d56Sopenharmony_ci module.__loader__ = self 1787db96d56Sopenharmony_ci return module 1797db96d56Sopenharmony_ci return set_loader_wrapper 1807db96d56Sopenharmony_ci 1817db96d56Sopenharmony_ci 1827db96d56Sopenharmony_cidef module_for_loader(fxn): 1837db96d56Sopenharmony_ci """Decorator to handle selecting the proper module for loaders. 1847db96d56Sopenharmony_ci 1857db96d56Sopenharmony_ci The decorated function is passed the module to use instead of the module 1867db96d56Sopenharmony_ci name. The module passed in to the function is either from sys.modules if 1877db96d56Sopenharmony_ci it already exists or is a new module. If the module is new, then __name__ 1887db96d56Sopenharmony_ci is set the first argument to the method, __loader__ is set to self, and 1897db96d56Sopenharmony_ci __package__ is set accordingly (if self.is_package() is defined) will be set 1907db96d56Sopenharmony_ci before it is passed to the decorated function (if self.is_package() does 1917db96d56Sopenharmony_ci not work for the module it will be set post-load). 1927db96d56Sopenharmony_ci 1937db96d56Sopenharmony_ci If an exception is raised and the decorator created the module it is 1947db96d56Sopenharmony_ci subsequently removed from sys.modules. 1957db96d56Sopenharmony_ci 1967db96d56Sopenharmony_ci The decorator assumes that the decorated function takes the module name as 1977db96d56Sopenharmony_ci the second argument. 1987db96d56Sopenharmony_ci 1997db96d56Sopenharmony_ci """ 2007db96d56Sopenharmony_ci warnings.warn('The import system now takes care of this automatically; ' 2017db96d56Sopenharmony_ci 'this decorator is slated for removal in Python 3.12', 2027db96d56Sopenharmony_ci DeprecationWarning, stacklevel=2) 2037db96d56Sopenharmony_ci @functools.wraps(fxn) 2047db96d56Sopenharmony_ci def module_for_loader_wrapper(self, fullname, *args, **kwargs): 2057db96d56Sopenharmony_ci with _module_to_load(fullname) as module: 2067db96d56Sopenharmony_ci module.__loader__ = self 2077db96d56Sopenharmony_ci try: 2087db96d56Sopenharmony_ci is_package = self.is_package(fullname) 2097db96d56Sopenharmony_ci except (ImportError, AttributeError): 2107db96d56Sopenharmony_ci pass 2117db96d56Sopenharmony_ci else: 2127db96d56Sopenharmony_ci if is_package: 2137db96d56Sopenharmony_ci module.__package__ = fullname 2147db96d56Sopenharmony_ci else: 2157db96d56Sopenharmony_ci module.__package__ = fullname.rpartition('.')[0] 2167db96d56Sopenharmony_ci # If __package__ was not set above, __import__() will do it later. 2177db96d56Sopenharmony_ci return fxn(self, module, *args, **kwargs) 2187db96d56Sopenharmony_ci 2197db96d56Sopenharmony_ci return module_for_loader_wrapper 2207db96d56Sopenharmony_ci 2217db96d56Sopenharmony_ci 2227db96d56Sopenharmony_ciclass _LazyModule(types.ModuleType): 2237db96d56Sopenharmony_ci 2247db96d56Sopenharmony_ci """A subclass of the module type which triggers loading upon attribute access.""" 2257db96d56Sopenharmony_ci 2267db96d56Sopenharmony_ci def __getattribute__(self, attr): 2277db96d56Sopenharmony_ci """Trigger the load of the module and return the attribute.""" 2287db96d56Sopenharmony_ci # All module metadata must be garnered from __spec__ in order to avoid 2297db96d56Sopenharmony_ci # using mutated values. 2307db96d56Sopenharmony_ci # Stop triggering this method. 2317db96d56Sopenharmony_ci self.__class__ = types.ModuleType 2327db96d56Sopenharmony_ci # Get the original name to make sure no object substitution occurred 2337db96d56Sopenharmony_ci # in sys.modules. 2347db96d56Sopenharmony_ci original_name = self.__spec__.name 2357db96d56Sopenharmony_ci # Figure out exactly what attributes were mutated between the creation 2367db96d56Sopenharmony_ci # of the module and now. 2377db96d56Sopenharmony_ci attrs_then = self.__spec__.loader_state['__dict__'] 2387db96d56Sopenharmony_ci attrs_now = self.__dict__ 2397db96d56Sopenharmony_ci attrs_updated = {} 2407db96d56Sopenharmony_ci for key, value in attrs_now.items(): 2417db96d56Sopenharmony_ci # Code that set the attribute may have kept a reference to the 2427db96d56Sopenharmony_ci # assigned object, making identity more important than equality. 2437db96d56Sopenharmony_ci if key not in attrs_then: 2447db96d56Sopenharmony_ci attrs_updated[key] = value 2457db96d56Sopenharmony_ci elif id(attrs_now[key]) != id(attrs_then[key]): 2467db96d56Sopenharmony_ci attrs_updated[key] = value 2477db96d56Sopenharmony_ci self.__spec__.loader.exec_module(self) 2487db96d56Sopenharmony_ci # If exec_module() was used directly there is no guarantee the module 2497db96d56Sopenharmony_ci # object was put into sys.modules. 2507db96d56Sopenharmony_ci if original_name in sys.modules: 2517db96d56Sopenharmony_ci if id(self) != id(sys.modules[original_name]): 2527db96d56Sopenharmony_ci raise ValueError(f"module object for {original_name!r} " 2537db96d56Sopenharmony_ci "substituted in sys.modules during a lazy " 2547db96d56Sopenharmony_ci "load") 2557db96d56Sopenharmony_ci # Update after loading since that's what would happen in an eager 2567db96d56Sopenharmony_ci # loading situation. 2577db96d56Sopenharmony_ci self.__dict__.update(attrs_updated) 2587db96d56Sopenharmony_ci return getattr(self, attr) 2597db96d56Sopenharmony_ci 2607db96d56Sopenharmony_ci def __delattr__(self, attr): 2617db96d56Sopenharmony_ci """Trigger the load and then perform the deletion.""" 2627db96d56Sopenharmony_ci # To trigger the load and raise an exception if the attribute 2637db96d56Sopenharmony_ci # doesn't exist. 2647db96d56Sopenharmony_ci self.__getattribute__(attr) 2657db96d56Sopenharmony_ci delattr(self, attr) 2667db96d56Sopenharmony_ci 2677db96d56Sopenharmony_ci 2687db96d56Sopenharmony_ciclass LazyLoader(Loader): 2697db96d56Sopenharmony_ci 2707db96d56Sopenharmony_ci """A loader that creates a module which defers loading until attribute access.""" 2717db96d56Sopenharmony_ci 2727db96d56Sopenharmony_ci @staticmethod 2737db96d56Sopenharmony_ci def __check_eager_loader(loader): 2747db96d56Sopenharmony_ci if not hasattr(loader, 'exec_module'): 2757db96d56Sopenharmony_ci raise TypeError('loader must define exec_module()') 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci @classmethod 2787db96d56Sopenharmony_ci def factory(cls, loader): 2797db96d56Sopenharmony_ci """Construct a callable which returns the eager loader made lazy.""" 2807db96d56Sopenharmony_ci cls.__check_eager_loader(loader) 2817db96d56Sopenharmony_ci return lambda *args, **kwargs: cls(loader(*args, **kwargs)) 2827db96d56Sopenharmony_ci 2837db96d56Sopenharmony_ci def __init__(self, loader): 2847db96d56Sopenharmony_ci self.__check_eager_loader(loader) 2857db96d56Sopenharmony_ci self.loader = loader 2867db96d56Sopenharmony_ci 2877db96d56Sopenharmony_ci def create_module(self, spec): 2887db96d56Sopenharmony_ci return self.loader.create_module(spec) 2897db96d56Sopenharmony_ci 2907db96d56Sopenharmony_ci def exec_module(self, module): 2917db96d56Sopenharmony_ci """Make the module load lazily.""" 2927db96d56Sopenharmony_ci module.__spec__.loader = self.loader 2937db96d56Sopenharmony_ci module.__loader__ = self.loader 2947db96d56Sopenharmony_ci # Don't need to worry about deep-copying as trying to set an attribute 2957db96d56Sopenharmony_ci # on an object would have triggered the load, 2967db96d56Sopenharmony_ci # e.g. ``module.__spec__.loader = None`` would trigger a load from 2977db96d56Sopenharmony_ci # trying to access module.__spec__. 2987db96d56Sopenharmony_ci loader_state = {} 2997db96d56Sopenharmony_ci loader_state['__dict__'] = module.__dict__.copy() 3007db96d56Sopenharmony_ci loader_state['__class__'] = module.__class__ 3017db96d56Sopenharmony_ci module.__spec__.loader_state = loader_state 3027db96d56Sopenharmony_ci module.__class__ = _LazyModule 303