diff options
Diffstat (limited to 'Lib/importlib/util.py')
-rw-r--r-- | Lib/importlib/util.py | 187 |
1 files changed, 184 insertions, 3 deletions
diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 1316437..6d73b1d 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -1,9 +1,18 @@ """Utility code for constructing importers, etc.""" -from ._bootstrap import module_for_loader -from ._bootstrap import set_loader -from ._bootstrap import set_package +from ._bootstrap import MAGIC_NUMBER +from ._bootstrap import cache_from_source +from ._bootstrap import decode_source +from ._bootstrap import source_from_cache +from ._bootstrap import spec_from_loader +from ._bootstrap import spec_from_file_location from ._bootstrap import _resolve_name +from ._bootstrap import _find_spec + +from contextlib import contextmanager +import functools +import sys +import warnings def resolve_name(name, package): @@ -19,3 +28,175 @@ def resolve_name(name, package): break level += 1 return _resolve_name(name[level:], package, level) + + +def _find_spec_from_path(name, path=None): + """Return the spec for the specified module. + + First, sys.modules is checked to see if the module was already imported. If + so, then sys.modules[name].__spec__ is returned. If that happens to be + set to None, then ValueError is raised. If the module is not in + sys.modules, then sys.meta_path is searched for a suitable spec with the + value of 'path' given to the finders. None is returned if no spec could + be found. + + Dotted names do not have their parent packages implicitly imported. You will + most likely need to explicitly import all parent packages in the proper + order for a submodule to get the correct spec. + + """ + if name not in sys.modules: + return _find_spec(name, path) + else: + module = sys.modules[name] + if module is None: + return None + try: + spec = module.__spec__ + except AttributeError: + raise ValueError('{}.__spec__ is not set'.format(name)) + else: + if spec is None: + raise ValueError('{}.__spec__ is None'.format(name)) + return spec + + +def find_spec(name, package=None): + """Return the spec for the specified module. + + First, sys.modules is checked to see if the module was already imported. If + so, then sys.modules[name].__spec__ is returned. If that happens to be + set to None, then ValueError is raised. If the module is not in + sys.modules, then sys.meta_path is searched for a suitable spec with the + value of 'path' given to the finders. None is returned if no spec could + be found. + + If the name is for submodule (contains a dot), the parent module is + automatically imported. + + The name and package arguments work the same as importlib.import_module(). + In other words, relative module names (with leading dots) work. + + """ + fullname = resolve_name(name, package) if name.startswith('.') else name + if fullname not in sys.modules: + parent_name = fullname.rpartition('.')[0] + if parent_name: + # Use builtins.__import__() in case someone replaced it. + parent = __import__(parent_name, fromlist=['__path__']) + return _find_spec(fullname, parent.__path__) + else: + return _find_spec(fullname, None) + else: + module = sys.modules[fullname] + if module is None: + return None + try: + spec = module.__spec__ + except AttributeError: + raise ValueError('{}.__spec__ is not set'.format(name)) + else: + if spec is None: + raise ValueError('{}.__spec__ is None'.format(name)) + return spec + + +@contextmanager +def _module_to_load(name): + is_reload = name in sys.modules + + module = sys.modules.get(name) + if not is_reload: + # This must be done before open() is called as the 'io' module + # implicitly imports 'locale' and would otherwise trigger an + # infinite loop. + module = type(sys)(name) + # This must be done before putting the module in sys.modules + # (otherwise an optimization shortcut in import.c becomes wrong) + module.__initializing__ = True + sys.modules[name] = module + try: + yield module + except Exception: + if not is_reload: + try: + del sys.modules[name] + except KeyError: + pass + finally: + module.__initializing__ = False + + +def set_package(fxn): + """Set __package__ on the returned module. + + This function is deprecated. + + """ + @functools.wraps(fxn) + def set_package_wrapper(*args, **kwargs): + warnings.warn('The import system now takes care of this automatically.', + DeprecationWarning, stacklevel=2) + module = fxn(*args, **kwargs) + if getattr(module, '__package__', None) is None: + module.__package__ = module.__name__ + if not hasattr(module, '__path__'): + module.__package__ = module.__package__.rpartition('.')[0] + return module + return set_package_wrapper + + +def set_loader(fxn): + """Set __loader__ on the returned module. + + This function is deprecated. + + """ + @functools.wraps(fxn) + def set_loader_wrapper(self, *args, **kwargs): + warnings.warn('The import system now takes care of this automatically.', + DeprecationWarning, stacklevel=2) + module = fxn(self, *args, **kwargs) + if getattr(module, '__loader__', None) is None: + module.__loader__ = self + return module + return set_loader_wrapper + + +def module_for_loader(fxn): + """Decorator to handle selecting the proper module for loaders. + + The decorated function is passed the module to use instead of the module + name. The module passed in to the function is either from sys.modules if + it already exists or is a new module. If the module is new, then __name__ + is set the first argument to the method, __loader__ is set to self, and + __package__ is set accordingly (if self.is_package() is defined) will be set + before it is passed to the decorated function (if self.is_package() does + not work for the module it will be set post-load). + + If an exception is raised and the decorator created the module it is + subsequently removed from sys.modules. + + The decorator assumes that the decorated function takes the module name as + the second argument. + + """ + warnings.warn('The import system now takes care of this automatically.', + DeprecationWarning, stacklevel=2) + @functools.wraps(fxn) + def module_for_loader_wrapper(self, fullname, *args, **kwargs): + with _module_to_load(fullname) as module: + module.__loader__ = self + try: + is_package = self.is_package(fullname) + except (ImportError, AttributeError): + pass + else: + if is_package: + module.__package__ = fullname + else: + module.__package__ = fullname.rpartition('.')[0] + # If __package__ was not set above, __import__() will do it later. + return fxn(self, module, *args, **kwargs) + + return module_for_loader_wrapper |