diff options
author | Eric Snow <ericsnowcurrently@gmail.com> | 2013-11-22 16:05:39 (GMT) |
---|---|---|
committer | Eric Snow <ericsnowcurrently@gmail.com> | 2013-11-22 16:05:39 (GMT) |
commit | b523f8433a8982e10eb41a3e2b37ee0e6d6a6e00 (patch) | |
tree | b38661db4903b7edc4042e7562b32720dd3687bf /Lib | |
parent | 9e6097ebe7bb99a4a22b949ef4b1563b21ad7166 (diff) | |
download | cpython-b523f8433a8982e10eb41a3e2b37ee0e6d6a6e00.zip cpython-b523f8433a8982e10eb41a3e2b37ee0e6d6a6e00.tar.gz cpython-b523f8433a8982e10eb41a3e2b37ee0e6d6a6e00.tar.bz2 |
Implement PEP 451 (ModuleSpec).
Diffstat (limited to 'Lib')
31 files changed, 2475 insertions, 813 deletions
@@ -16,7 +16,7 @@ except ImportError: # Platform doesn't support dynamic loading. load_dynamic = None -from importlib._bootstrap import SourcelessFileLoader, _ERR_MSG +from importlib._bootstrap import SourcelessFileLoader, _ERR_MSG, _SpecMethods from importlib import machinery from importlib import util @@ -162,11 +162,17 @@ class _LoadSourceCompatibility(_HackedGetData, machinery.SourceFileLoader): def load_source(name, pathname, file=None): - _LoadSourceCompatibility(name, pathname, file).load_module(name) - module = sys.modules[name] + loader = _LoadSourceCompatibility(name, pathname, file) + spec = util.spec_from_file_location(name, pathname, loader=loader) + methods = _SpecMethods(spec) + if name in sys.modules: + module = methods.exec(sys.modules[name]) + else: + module = methods.load() # To allow reloading to potentially work, use a non-hacked loader which # won't rely on a now-closed file object. module.__loader__ = machinery.SourceFileLoader(name, pathname) + module.__spec__.loader = module.__loader__ return module @@ -177,11 +183,17 @@ class _LoadCompiledCompatibility(_HackedGetData, SourcelessFileLoader): def load_compiled(name, pathname, file=None): """**DEPRECATED**""" - _LoadCompiledCompatibility(name, pathname, file).load_module(name) - module = sys.modules[name] + loader = _LoadCompiledCompatibility(name, pathname, file) + spec = util.spec_from_file_location(name, pathname, loader=loader) + methods = _SpecMethods(spec) + if name in sys.modules: + module = methods.exec(sys.modules[name]) + else: + module = methods.load() # To allow reloading to potentially work, use a non-hacked loader which # won't rely on a now-closed file object. module.__loader__ = SourcelessFileLoader(name, pathname) + module.__spec__.loader = module.__loader__ return module @@ -196,7 +208,13 @@ def load_package(name, path): break else: raise ValueError('{!r} is not a package'.format(path)) - return machinery.SourceFileLoader(name, path).load_module(name) + spec = util.spec_from_file_location(name, path, + submodule_search_locations=[]) + methods = _SpecMethods(spec) + if name in sys.modules: + return methods.exec(sys.modules[name]) + else: + return methods.load() def load_module(name, file, filename, details): diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py index e56e9c3..3e969bb 100644 --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -46,19 +46,42 @@ def invalidate_caches(): finder.invalidate_caches() -def find_loader(name, path=None): - """Find the loader for the specified module. +def find_spec(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].__loader__ is returned. If that happens to be + 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 loader with the - value of 'path' given to the finders. None is returned if no loader could + 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 loader. + order for a submodule to get the correct spec. + + """ + if name not in sys.modules: + return _bootstrap._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 + + +# XXX Deprecate... +def find_loader(name, path=None): + """Return the loader for the specified module. + + This is a backward-compatible wrapper around find_spec(). """ try: @@ -71,7 +94,18 @@ def find_loader(name, path=None): pass except AttributeError: raise ValueError('{}.__loader__ is not set'.format(name)) - return _bootstrap._find_module(name, path) + + spec = _bootstrap._find_spec(name, path) + # We won't worry about malformed specs (missing attributes). + if spec is None: + return None + if spec.loader is None: + if spec.submodule_search_locations is None: + raise ImportError('spec for {} missing loader'.format(name), + name=name) + raise ImportError('namespace packages do not have loaders', + name=name) + return spec.loader def import_module(name, package=None): @@ -106,7 +140,11 @@ def reload(module): """ if not module or not isinstance(module, types.ModuleType): raise TypeError("reload() argument must be module") - name = module.__name__ + try: + name = module.__spec__.name + except AttributeError: + name = module.__name__ + if sys.modules.get(name) is not module: msg = "module {} not in sys.modules" raise ImportError(msg.format(name), name=name) @@ -118,13 +156,11 @@ def reload(module): if parent_name and parent_name not in sys.modules: msg = "parent {!r} not in sys.modules" raise ImportError(msg.format(parent_name), name=parent_name) - loader = _bootstrap._find_module(name, None) - if loader is None: - raise ImportError(_bootstrap._ERR_MSG.format(name), name=name) - module.__loader__ = loader - loader.load_module(name) + spec = module.__spec__ = _bootstrap._find_spec(name, None, module) + methods = _bootstrap._SpecMethods(spec) + methods.exec(module) # The module may have replaced itself in sys.modules! - return sys.modules[module.__name__] + return sys.modules[name] finally: try: del _RELOADING[name] diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 77fae0a..a6db00b 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -84,13 +84,11 @@ def _path_is_mode_type(path, mode): return (stat_info.st_mode & 0o170000) == mode -# XXX Could also expose Modules/getpath.c:isfile() def _path_isfile(path): """Replacement for os.path.isfile.""" return _path_is_mode_type(path, 0o100000) -# XXX Could also expose Modules/getpath.c:isdir() def _path_isdir(path): """Replacement for os.path.isdir.""" if not path: @@ -128,6 +126,10 @@ def _wrap(new, old): new.__dict__.update(old.__dict__) +def _new_module(name): + return type(sys)(name) + + _code_type = type(_wrap.__code__) @@ -232,6 +234,23 @@ class _DummyModuleLock: return '_DummyModuleLock({!r}) at {}'.format(self.name, id(self)) +class _ModuleLockManager: + + def __init__(self, name): + self._name = name + self._lock = None + + def __enter__(self): + try: + self._lock = _get_module_lock(self._name) + finally: + _imp.release_lock() + self._lock.acquire() + + def __exit__(self, *args, **kwargs): + self._lock.release() + + # The following two functions are for consumption by Python/import.c. def _get_module_lock(name): @@ -485,124 +504,6 @@ def _verbose_message(message, *args, verbosity=1): print(message.format(*args), file=sys.stderr) -class _ManageReload: - - def __init__(self, name): - self._name = name - - def __enter__(self): - self._is_reload = self._name in sys.modules - - def __exit__(self, *args): - if any(arg is not None for arg in args) and not self._is_reload: - try: - del sys.modules[self._name] - except KeyError: - pass - - -# Written as a class only because contextlib is not available. -class _ModuleManager(_ManageReload): - - """Context manager which returns the module to be loaded. - - Does the proper unloading from sys.modules upon failure. - - """ - - def __init__(self, name, *, reset_name=True): - """Prepare the context manager. - - The reset_name argument specifies whether to unconditionally reset - the __name__ attribute if the module is found to be a reload. - """ - super().__init__(name) - self._reset_name = reset_name - - def __enter__(self): - super().__enter__() - self._module = sys.modules.get(self._name) - if not self._is_reload: - # This must be done before open() is called as the 'io' module - # implicitly imports 'locale' and would otherwise trigger an - # infinite loop. - self._module = type(_io)(self._name) - # This must be done before putting the module in sys.modules - # (otherwise an optimization shortcut in import.c becomes wrong) - self._module.__initializing__ = True - sys.modules[self._name] = self._module - elif self._reset_name: - try: - self._module.__name__ = self._name - except AttributeError: - pass - return self._module - - def __exit__(self, *args): - self._module.__initializing__ = False - del self._module - super().__exit__(*args) - - -def module_to_load(name, *, reset_name=True): - """Return a context manager which provides the module object to load. - - If reset_name is true, reset the module's __name__ to 'name'. - """ - # Hiding _ModuleManager behind a function for better naming. - return _ModuleManager(name, reset_name=reset_name) - - -def _init_package_attrs(loader, module): - """Set __package__ and __path__ based on what loader.is_package() says.""" - name = module.__name__ - try: - is_package = loader.is_package(name) - except ImportError: - pass - else: - if is_package: - module.__package__ = name - module.__path__ = [] - else: - module.__package__ = name.rpartition('.')[0] - - -def _init_file_attrs(loader, module): - """Set __file__ and __path__ based on loader.get_filename().""" - try: - module.__file__ = loader.get_filename(module.__name__) - except ImportError: - pass - else: - if module.__name__ == module.__package__: - module.__path__.append(_path_split(module.__file__)[0]) - - -def set_package(fxn): - """Set __package__ on the returned module.""" - def set_package_wrapper(*args, **kwargs): - 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 - _wrap(set_package_wrapper, fxn) - return set_package_wrapper - - -def set_loader(fxn): - """Set __loader__ on the returned module.""" - def set_loader_wrapper(self, *args, **kwargs): - module = fxn(self, *args, **kwargs) - if getattr(module, '__loader__', None) is None: - module.__loader__ = self - return module - _wrap(set_loader_wrapper, fxn) - return set_loader_wrapper - - def _check_name(method): """Decorator to verify that the module being requested matches the one the loader can handle. @@ -656,6 +557,19 @@ def _find_module_shim(self, fullname): return loader +def _load_module_shim(self, fullname): + """Load the specified module into sys.modules and return it.""" + # XXX Deprecation Warning here... + spec = spec_from_loader(fullname, self) + methods = _SpecMethods(spec) + if fullname in sys.modules: + module = sys.modules[fullname] + methods.exec(module) + return sys.modules[fullname] + else: + return methods.load() + + def _validate_bytecode_header(data, source_stats=None, name=None, path=None): """Validate the header of the passed-in bytecode against source_stats (if given) and returning the bytecode that can be compiled by compile(). @@ -745,6 +659,556 @@ def decode_source(source_bytes): return newline_decoder.decode(source_bytes.decode(encoding[0])) +# Module specifications ####################################################### + +def _module_repr(module): + # The implementation of ModuleType__repr__(). + loader = getattr(module, '__loader__', None) + if hasattr(loader, 'module_repr'): + # XXX Deprecation Warning here... + try: + return loader.module_repr(module) + except Exception: + pass + try: + spec = module.__spec__ + except AttributeError: + pass + else: + if spec is not None: + return _SpecMethods(spec).module_repr() + + # We could use module.__class__.__name__ instead of 'module' in the + # various repr permutations. + try: + name = module.__name__ + except AttributeError: + name = '?' + try: + filename = module.__file__ + except AttributeError: + if loader is None: + return '<module {!r}>'.format(name) + else: + return '<module {!r} ({!r})>'.format(name, loader) + else: + return '<module {!r} from {!r}>'.format(name, filename) + + +class _installed_safely: + + def __init__(self, module): + self._module = module + self._spec = module.__spec__ + + def __enter__(self): + # This must be done before putting the module in sys.modules + # (otherwise an optimization shortcut in import.c becomes + # wrong) + self._spec._initializing = True + sys.modules[self._spec.name] = self._module + + def __exit__(self, *args): + try: + spec = self._spec + if any(arg is not None for arg in args): + try: + del sys.modules[spec.name] + except KeyError: + pass + else: + _verbose_message('import {!r} # {!r}', spec.name, spec.loader) + finally: + self._spec._initializing = False + + +class ModuleSpec: + """The specification for a module, used for loading. + + A module's spec is the source for information about the module. For + data associated with the module, including source, use the spec's + loader. + + `name` is the absolute name of the module. `loader` is the loader + to use when loading the module. `parent` is the name of the + package the module is in. The parent is derived from the name. + + `is_package` determines if the module is considered a package or + not. On modules this is reflected by the `__path__` attribute. + + `origin` is the specific location used by the loader from which to + load the module, if that information is available. When filename is + set, origin will match. + + `has_location` indicates that a spec's "origin" reflects a location. + When this is True, `__file__` attribute of the module is set. + + `cached` is the location of the cached bytecode file, if any. It + corresponds to the `__cached__` attribute. + + `submodule_search_locations` is the sequence of path entries to + search when importing submodules. If set, is_package should be + True--and False otherwise. + + Packages are simply modules that (may) have submodules. If a spec + has a non-None value in `submodule_search_locations`, the import + system will consider modules loaded from the spec as packages. + + Only finders (see importlib.abc.MetaPathFinder and + importlib.abc.PathEntryFinder) should modify ModuleSpec instances. + + """ + + def __init__(self, name, loader, *, origin=None, loader_state=None, + is_package=None): + self.name = name + self.loader = loader + self.origin = origin + self.loader_state = loader_state + self.submodule_search_locations = [] if is_package else None + + # file-location attributes + self._set_fileattr = False + self._cached = None + + def __repr__(self): + args = ['name={!r}'.format(self.name), + 'loader={!r}'.format(self.loader)] + if self.origin is not None: + args.append('origin={!r}'.format(self.origin)) + if self.submodule_search_locations is not None: + args.append('submodule_search_locations={}' + .format(self.submodule_search_locations)) + return '{}({})'.format(self.__class__.__name__, ', '.join(args)) + + def __eq__(self, other): + smsl = self.submodule_search_locations + try: + return (self.name == other.name and + self.loader == other.loader and + self.origin == other.origin and + smsl == other.submodule_search_locations and + self.cached == other.cached and + self.has_location == other.has_location) + except AttributeError: + return False + + @property + def cached(self): + if self._cached is None: + if self.origin is not None and self._set_fileattr: + filename = self.origin + if filename.endswith(tuple(SOURCE_SUFFIXES)): + try: + self._cached = cache_from_source(filename) + except NotImplementedError: + pass + elif filename.endswith(tuple(BYTECODE_SUFFIXES)): + self._cached = filename + return self._cached + + @cached.setter + def cached(self, cached): + self._cached = cached + + @property + def parent(self): + """The name of the module's parent.""" + if self.submodule_search_locations is None: + return self.name.rpartition('.')[0] + else: + return self.name + + @property + def has_location(self): + return self._set_fileattr + + +def spec_from_loader(name, loader, *, origin=None, is_package=None): + """Return a module spec based on various loader methods.""" + +# if hasattr(loader, 'get_data'): + if hasattr(loader, 'get_filename'): + if is_package is None: + return spec_from_file_location(name, loader=loader) + search = [] if is_package else None + return spec_from_file_location(name, loader=loader, + submodule_search_locations=search) + + if is_package is None: + if hasattr(loader, 'is_package'): + try: + is_package = loader.is_package(name) + except ImportError: + is_package = None # aka, undefined + else: + # the default + is_package = False + + return ModuleSpec(name, loader, origin=origin, is_package=is_package) + + +_POPULATE = object() + + +def spec_from_file_location(name, location=None, *, loader=None, + submodule_search_locations=_POPULATE): + """Return a module spec based on a file location. + + To indicate that the module is a package, set + submodule_search_locations to a list of directory paths. An + empty list is sufficient, though its not otherwise useful to the + import system. + + The loader must take a spec as its only __init__() arg. + + """ + if location is None: + # The caller may simply want a partially populated location- + # oriented spec. So we set the location to a bogus value and + # fill in as much as we can. + location = '<unknown>' + if hasattr(loader, 'get_filename'): + # ExecutionLoader + try: + location = loader.get_filename(name) + except ImportError: + pass + + # If the location is on the filesystem, but doesn't actually exist, + # we could return None here, indicating that the location is not + # valid. However, we don't have a good way of testing since an + # indirect location (e.g. a zip file or URL) will look like a + # non-existent file relative to the filesystem. + + spec = ModuleSpec(name, loader, origin=location) + spec._set_fileattr = True + + # Pick a loader if one wasn't provided. + if loader is None: + for loader_class, suffixes in _get_supported_file_loaders(): + if location.endswith(tuple(suffixes)): + loader = loader_class(name, location) + spec.loader = loader + break + else: + return None + + # Set submodule_search_paths appropriately. + if submodule_search_locations is _POPULATE: + # Check the loader. + if hasattr(loader, 'is_package'): + try: + is_package = loader.is_package(name) + except ImportError: + pass + else: + if is_package: + spec.submodule_search_locations = [] + else: + spec.submodule_search_locations = submodule_search_locations + if spec.submodule_search_locations == []: + if location: + dirname = _path_split(location)[0] + spec.submodule_search_locations.append(dirname) + + return spec + + +def _spec_from_module(module, loader=None, origin=None): + # This function is meant for use in _setup(). + try: + spec = module.__spec__ + except AttributeError: + pass + else: + if spec is not None: + return spec + + name = module.__name__ + if loader is None: + try: + loader = module.__loader__ + except AttributeError: + # loader will stay None. + pass + try: + location = module.__file__ + except AttributeError: + location = None + if origin is None: + if location is None: + try: + origin = loader._ORIGIN + except AttributeError: + origin = None + else: + origin = location + try: + cached = module.__cached__ + except AttributeError: + cached = None + try: + submodule_search_locations = list(module.__path__) + except AttributeError: + submodule_search_locations = None + + spec = ModuleSpec(name, loader, origin=origin) + spec._set_fileattr = False if location is None else True + spec.cached = cached + spec.submodule_search_locations = submodule_search_locations + return spec + + +class _SpecMethods: + + """Convenience wrapper around spec objects to provide spec-specific + methods.""" + + def __init__(self, spec): + self.spec = spec + + @classmethod + def from_module(cls, module): + """Create a spec from a module's attributes.""" + try: + spec = module.__spec__ + except AttributeError: + try: + loader = spec.__loader__ + except AttributeError: + spec = _find_spec(module.__name__) + if spec is None: + spec = spec_from_loader(module.__name__, loader) + else: + spec = spec_from_loader(module.__name__, loader) + return cls(spec) + + def module_repr(self): + """Return the repr to use for the module.""" + # We mostly replicate _module_repr() using the spec attributes. + spec = self.spec + name = '?' if spec.name is None else spec.name + if spec.origin is None: + if spec.loader is None: + return '<module {!r}>'.format(name) + else: + return '<module {!r} ({!r})>'.format(name, spec.loader) + else: + if spec.has_location: + return '<module {!r} from {!r}>'.format(name, spec.origin) + else: + return '<module {!r} ({})>'.format(spec.name, spec.origin) + + def init_module_attrs(self, module, *, _override=False, _force_name=True): + """Set the module's attributes. + + All missing import-related module attributes will be set. Here + is how the spec attributes map onto the module: + + spec.name -> module.__name__ + spec.loader -> module.__loader__ + spec.parent -> module.__package__ + spec -> module.__spec__ + + Optional: + spec.origin -> module.__file__ (if spec.set_fileattr is true) + spec.cached -> module.__cached__ (if __file__ also set) + spec.submodule_search_locations -> module.__path__ (if set) + + """ + spec = self.spec + + # The passed in module may be not support attribute assignment, + # in which case we simply don't set the attributes. + + # __name__ + if (_override or _force_name or + getattr(module, '__name__', None) is None): + try: + module.__name__ = spec.name + except AttributeError: + pass + + # __loader__ + if _override or getattr(module, '__loader__', None) is None: + loader = spec.loader + if loader is None: + # A backward compatibility hack. + if spec.submodule_search_locations is not None: + loader = _NamespaceLoader.__new__(_NamespaceLoader) + loader._path = spec.submodule_search_locations + try: + module.__loader__ = loader + except AttributeError: + pass + + # __package__ + if _override or getattr(module, '__package__', None) is None: + try: + module.__package__ = spec.parent + except AttributeError: + pass + + # __spec__ + try: + module.__spec__ = spec + except AttributeError: + pass + + # __path__ + if _override or getattr(module, '__path__', None) is None: + if spec.submodule_search_locations is not None: + try: + module.__path__ = spec.submodule_search_locations + except AttributeError: + pass + + if spec.has_location: + # __file__ + if _override or getattr(module, '__file__', None) is None: + try: + module.__file__ = spec.origin + except AttributeError: + pass + + # __cached__ + if _override or getattr(module, '__cached__', None) is None: + if spec.cached is not None: + try: + module.__cached__ = spec.cached + except AttributeError: + pass + + def create(self): + """Return a new module to be loaded. + + The import-related module attributes are also set with the + appropriate values from the spec. + + """ + spec = self.spec + # Typically loaders will not implement create_module(). + if hasattr(spec.loader, 'create_module'): + # If create_module() returns `None` it means the default + # module creation should be used. + module = spec.loader.create_module(spec) + else: + module = None + if module is None: + # This must be done before open() is ever called as the 'io' + # module implicitly imports 'locale' and would otherwise + # trigger an infinite loop. + module = _new_module(spec.name) + self.init_module_attrs(module) + return module + + def _exec(self, module): + """Do everything necessary to execute the module. + + The namespace of `module` is used as the target of execution. + This method uses the loader's `exec_module()` method. + + """ + self.spec.loader.exec_module(module) + + # Used by importlib.reload() and _load_module_shim(). + def exec(self, module): + """Execute the spec in an existing module's namespace.""" + name = self.spec.name + _imp.acquire_lock() + with _ModuleLockManager(name): + if sys.modules.get(name) is not module: + msg = 'module {} not in sys.modules'.format(name) + raise ImportError(msg, name=name) + if self.spec.loader is None: + if self.spec.submodule_search_locations is None: + raise ImportError('missing loader', name=self.spec.name) + # namespace package + self.init_module_attrs(module, _override=True) + return module + self.init_module_attrs(module, _override=True) + if not hasattr(self.spec.loader, 'exec_module'): + # XXX DeprecationWarning goes here... + self.spec.loader.load_module(name) + else: + self._exec(module) + return sys.modules[name] + + def _load_backward_compatible(self): + # XXX DeprecationWarning goes here... + spec = self.spec + # The module must be in sys.modules! + spec.loader.load_module(spec.name) + module = sys.modules[spec.name] + if getattr(module, '__loader__', None) is None: + try: + module.__loader__ = spec.loader + except AttributeError: + pass + if getattr(module, '__package__', None) is None: + try: + # Since module.__path__ may not line up with + # spec.submodule_search_paths, we can't necessarily rely + # on spec.parent here. + module.__package__ = module.__name__ + if not hasattr(module, '__path__'): + module.__package__ = spec.name.rpartition('.')[0] + except AttributeError: + pass + if getattr(module, '__spec__', None) is None: + try: + module.__spec__ = spec + except AttributeError: + pass + return module + + # XXX If we don't end up using this for pythonrun.c/runpy, we should + # get rid of it. + def _load_existing(self, module): + """Exec the spec'ed module into an existing module's namespace.""" + # For use by runpy. + with _installed_safely(module): + loaded = self.exec(module) + return loaded + + def _load_unlocked(self): + # A helper for direct use by the import system. + if self.spec.loader is not None: + # not a namespace package + if not hasattr(self.spec.loader, 'exec_module'): + return self._load_backward_compatible() + + module = self.create() + with _installed_safely(module): + if self.spec.loader is None: + if self.spec.submodule_search_locations is None: + raise ImportError('missing loader', name=self.spec.name) + # A namespace package so do nothing. + else: + self._exec(module) + + # We don't ensure that the import-related module attributes get + # set in the sys.modules replacement case. Such modules are on + # their own. + return sys.modules[self.spec.name] + + # A method used during testing of _load_unlocked() and by + # _load_module_shim(). + def load(self): + """Return a new module object, loaded by the spec's loader. + + The module is not added to its parent. + + If a module is already in sys.modules, that existing module gets + clobbered. + + """ + _imp.acquire_lock() + with _ModuleLockManager(self.spec.name): + return self._load_unlocked() + + # Loaders ##################################################################### class BuiltinImporter: @@ -756,29 +1220,47 @@ class BuiltinImporter: """ - @classmethod - def module_repr(cls, module): + @staticmethod + def module_repr(module): + # XXX deprecate return '<module {!r} (built-in)>'.format(module.__name__) @classmethod + def find_spec(cls, fullname, path=None, target=None): + if path is not None: + return None + if _imp.is_builtin(fullname): + return spec_from_loader(fullname, cls, origin='built-in') + else: + return None + + @classmethod def find_module(cls, fullname, path=None): """Find the built-in module. If 'path' is ever specified then the search is considered a failure. """ - if path is not None: - return None - return cls if _imp.is_builtin(fullname) else None + spec = cls.find_spec(fullname, path) + return spec.loader if spec is not None else None + + @staticmethod + def exec_module(module): + spec = module.__spec__ + name = spec.name + if not _imp.is_builtin(name): + raise ImportError('{} is not a built-in module'.format(name), + name=name) + _call_with_frames_removed(_imp.init_builtin, name) + # Have to manually initialize attributes since init_builtin() is not + # going to do it for us. + # XXX: Create _imp.exec_builtin(module) + _SpecMethods(spec).init_module_attrs(sys.modules[name]) @classmethod - @set_package - @set_loader - @_requires_builtin def load_module(cls, fullname): """Load a built-in module.""" - with _ManageReload(fullname): - return _call_with_frames_removed(_imp.init_builtin, fullname) + return _load_module_shim(cls, fullname) @classmethod @_requires_builtin @@ -796,6 +1278,7 @@ class BuiltinImporter: @_requires_builtin def is_package(cls, fullname): """Return False as built-in modules are never packages.""" + # XXX DeprecationWarning here... return False @@ -808,26 +1291,36 @@ class FrozenImporter: """ - @classmethod - def module_repr(cls, m): + @staticmethod + def module_repr(m): + # XXX deprecate return '<module {!r} (frozen)>'.format(m.__name__) @classmethod + def find_spec(cls, fullname, path=None, target=None): + if _imp.is_frozen(fullname): + return spec_from_loader(fullname, cls, origin='frozen') + else: + return None + + @classmethod def find_module(cls, fullname, path=None): """Find a frozen module.""" return cls if _imp.is_frozen(fullname) else None + @staticmethod + def exec_module(module): + name = module.__spec__.name + if not _imp.is_frozen(name): + raise ImportError('{} is not a frozen module'.format(name), + name=name) + code = _call_with_frames_removed(_imp.get_frozen_object, name) + exec(code, module.__dict__) + @classmethod - @set_package - @set_loader - @_requires_frozen def load_module(cls, fullname): """Load a frozen module.""" - with _ManageReload(fullname): - m = _call_with_frames_removed(_imp.init_frozen, fullname) - # Let our own module_repr() method produce a suitable repr. - del m.__file__ - return m + return _load_module_shim(cls, fullname) @classmethod @_requires_frozen @@ -850,8 +1343,7 @@ class FrozenImporter: class WindowsRegistryFinder: - """Meta path finder for modules declared in the Windows registry. - """ + """Meta path finder for modules declared in the Windows registry.""" REGISTRY_KEY = ( 'Software\\Python\\PythonCore\\{sys_version}' @@ -884,8 +1376,8 @@ class WindowsRegistryFinder: return filepath @classmethod - def find_module(cls, fullname, path=None): - """Find module named in the registry.""" + def find_spec(cls, fullname, path=None, target=None): + # XXX untested! Need a Windows person to write tests (otherwise mock out appropriately) filepath = cls._search_registry(fullname) if filepath is None: return None @@ -895,7 +1387,18 @@ class WindowsRegistryFinder: return None for loader, suffixes in _get_supported_file_loaders(): if filepath.endswith(tuple(suffixes)): - return loader(fullname, filepath) + spec = spec_from_loader(fullname, loader(fullname, filepath), + origin=filepath) + return spec + + @classmethod + def find_module(cls, fullname, path=None): + """Find module named in the registry.""" + spec = self.find_spec(fullname, path) + if spec is not None: + return spec.loader + else: + return None class _LoaderBasics: @@ -903,6 +1406,7 @@ class _LoaderBasics: """Base class of common code needed by both SourceLoader and SourcelessFileLoader.""" + # XXX deprecate? def is_package(self, fullname): """Concrete implementation of InspectLoader.is_package by checking if the path returned by get_filename has a filename of '__init__.py'.""" @@ -911,32 +1415,15 @@ class _LoaderBasics: tail_name = fullname.rpartition('.')[2] return filename_base == '__init__' and tail_name != '__init__' - def init_module_attrs(self, module): - """Set various attributes on the module. + def exec_module(self, module): + """Execute the module.""" + code = self.get_code(module.__name__) + if code is None: + raise ImportError('cannot load module {!r} when get_code() ' + 'returns None'.format(module.__name__)) + _call_with_frames_removed(exec, code, module.__dict__) - ExecutionLoader.init_module_attrs() is used to set __loader__, - __package__, __file__, and optionally __path__. The __cached__ attribute - is set using imp.cache_from_source() and __file__. - """ - module.__loader__ = self # Loader - _init_package_attrs(self, module) # InspectLoader - _init_file_attrs(self, module) # ExecutionLoader - if hasattr(module, '__file__'): # SourceLoader - try: - module.__cached__ = cache_from_source(module.__file__) - except NotImplementedError: - pass - - def load_module(self, fullname): - """Load the specified module into sys.modules and return it.""" - with module_to_load(fullname) as module: - self.init_module_attrs(module) - code = self.get_code(fullname) - if code is None: - raise ImportError('cannot load module {!r} when get_code() ' - 'returns None'.format(fullname)) - _call_with_frames_removed(exec, code, module.__dict__) - return module + load_module = _load_module_shim class SourceLoader(_LoaderBasics): @@ -1063,7 +1550,9 @@ class FileLoader: @_check_name def load_module(self, fullname): """Load a module from a file.""" - # Issue #14857: Avoid the zero-argument form so the implementation + # The only reason for this method is for the name check. + + # Issue #14857: Avoid the zero-argument form of super so the implementation # of that form can be updated without breaking the frozen module return super(FileLoader, self).load_module(fullname) @@ -1125,10 +1614,6 @@ class SourcelessFileLoader(FileLoader, _LoaderBasics): """Loader which handles sourceless file imports.""" - def init_module_attrs(self, module): - super().init_module_attrs(module) - module.__cached__ = module.__file__ - def get_code(self, fullname): path = self.get_filename(fullname) data = self.get_data(path) @@ -1156,18 +1641,22 @@ class ExtensionFileLoader: self.name = name self.path = path + def exec_module(self, module): + # XXX create _imp.exec_dynamic() + spec = module.__spec__ + fullname = spec.name + module = _call_with_frames_removed(_imp.load_dynamic, + fullname, self.path) + _verbose_message('extension module loaded from {!r}', self.path) + if self.is_package(fullname) and not hasattr(module, '__path__'): + module.__path__ = [_path_split(self.path)[0]] + _SpecMethods(spec).init_module_attrs(module) + + # XXX deprecate @_check_name - @set_package - @set_loader def load_module(self, fullname): """Load an extension module.""" - with _ManageReload(fullname): - module = _call_with_frames_removed(_imp.load_dynamic, - fullname, self.path) - _verbose_message('extension module loaded from {!r}', self.path) - if self.is_package(fullname) and not hasattr(module, '__path__'): - module.__path__ = [_path_split(self.path)[0]] - return module + return _load_module_shim(self, fullname) def is_package(self, fullname): """Return True if the extension module is a package.""" @@ -1220,11 +1709,12 @@ class _NamespacePath: # If the parent's path has changed, recalculate _path parent_path = tuple(self._get_parent_path()) # Make a copy if parent_path != self._last_parent_path: - loader, new_path = self._path_finder(self._name, parent_path) + spec = self._path_finder(self._name, parent_path) # Note that no changes are made if a loader is returned, but we # do remember the new parent path - if loader is None: - self._path = new_path + if spec is not None and spec.loader is None: + if spec.submodule_search_locations: + self._path = spec.submodule_search_locations self._last_parent_path = parent_path # Save the copy return self._path @@ -1244,10 +1734,12 @@ class _NamespacePath: self._path.append(item) -class NamespaceLoader: +# We use this exclusively in init_module_attrs() for backward-compatibility. +class _NamespaceLoader: def __init__(self, name, path, path_finder): self._path = _NamespacePath(name, path, path_finder) + # XXX Deprecate @classmethod def module_repr(cls, module): return '<module {!r} (namespace)>'.format(module.__name__) @@ -1261,17 +1753,11 @@ class NamespaceLoader: def get_code(self, fullname): return compile('', '<string>', 'exec', dont_inherit=True) - def init_module_attrs(self, module): - module.__loader__ = self - module.__package__ = module.__name__ - + # XXX Deprecate def load_module(self, fullname): """Load a namespace module.""" _verbose_message('namespace module loaded with path {!r}', self._path) - with module_to_load(fullname) as module: - self.init_module_attrs(module) - module.__path__ = self._path - return module + return _load_module_shim(self, fullname) # Finders ##################################################################### @@ -1323,7 +1809,20 @@ class PathFinder: return finder @classmethod - def _get_loader(cls, fullname, path): + def _legacy_get_spec(cls, fullname, finder): + if hasattr(finder, 'find_loader'): + loader, portions = finder.find_loader(fullname) + else: + loader = finder.find_module(fullname) + portions = None + if loader is not None: + return spec_from_loader(fullname, loader) + spec = ModuleSpec(fullname, None) + spec.submodule_search_locations = portions + return spec + + @classmethod + def _get_spec(cls, fullname, path, target=None): """Find the loader or namespace_path for this module/package name.""" # If this ends up being a namespace package, namespace_path is # the list of paths that will become its __path__ @@ -1333,38 +1832,58 @@ class PathFinder: continue finder = cls._path_importer_cache(entry) if finder is not None: - if hasattr(finder, 'find_loader'): - loader, portions = finder.find_loader(fullname) + if hasattr(finder, 'find_spec'): + spec = finder.find_spec(fullname, target) else: - loader = finder.find_module(fullname) - portions = [] - if loader is not None: - # We found a loader: return it immediately. - return loader, namespace_path + spec = cls._legacy_get_spec(fullname, finder) + if spec is None: + continue + if spec.loader is not None: + return spec + portions = spec.submodule_search_locations + if portions is None: + raise ImportError('spec missing loader') # This is possibly part of a namespace package. # Remember these path entries (if any) for when we # create a namespace package, and continue iterating # on path. namespace_path.extend(portions) else: - return None, namespace_path + spec = ModuleSpec(fullname, None) + spec.submodule_search_locations = namespace_path + return spec @classmethod - def find_module(cls, fullname, path=None): - """Find the module on sys.path or 'path' based on sys.path_hooks and + def find_spec(cls, fullname, path=None, target=None): + """find the module on sys.path or 'path' based on sys.path_hooks and sys.path_importer_cache.""" if path is None: path = sys.path - loader, namespace_path = cls._get_loader(fullname, path) - if loader is not None: - return loader - else: + spec = cls._get_spec(fullname, path, target) + if spec is None: + return None + elif spec.loader is None: + namespace_path = spec.submodule_search_locations if namespace_path: # We found at least one namespace path. Return a - # loader which can create the namespace package. - return NamespaceLoader(fullname, namespace_path, cls._get_loader) + # spec which can create the namespace package. + spec.origin = 'namespace' + spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec) + return spec else: return None + else: + return spec + + @classmethod + def find_module(cls, fullname, path=None): + """find the module on sys.path or 'path' based on sys.path_hooks and + sys.path_importer_cache.""" + # XXX Deprecation warning here. + spec = cls.find_spec(fullname, path) + if spec is None: + return None + return spec.loader class FileFinder: @@ -1399,6 +1918,24 @@ class FileFinder: def find_loader(self, fullname): """Try to find a loader for the specified module, or the namespace package portions. Returns (loader, list-of-portions).""" + spec = self.find_spec(fullname) + if spec is None: + return None, [] + return spec.loader, spec.submodule_search_locations or [] + + def _get_spec(self, loader_class, fullname, path, submodule_search_locations, target): + loader = loader_class(fullname, path) + try: + get_spec = loader._get_spec + except AttributeError: + return spec_from_file_location(fullname, path, loader=loader, + submodule_search_locations=submodule_search_locations) + else: + return get_spec(fullname, path, submodule_search_locations, target) + + def find_spec(self, fullname, target=None): + """Try to find a loader for the specified module, or the namespace + package portions. Returns (loader, list-of-portions).""" is_namespace = False tail_module = fullname.rpartition('.')[2] try: @@ -1418,26 +1955,28 @@ class FileFinder: # Check if the module is the name of a directory (and thus a package). if cache_module in cache: base_path = _path_join(self.path, tail_module) - for suffix, loader in self._loaders: + for suffix, loader_class in self._loaders: init_filename = '__init__' + suffix full_path = _path_join(base_path, init_filename) if _path_isfile(full_path): - return (loader(fullname, full_path), [base_path]) + return self._get_spec(loader_class, fullname, full_path, [base_path], target) else: # If a namespace package, return the path if we don't # find a module in the next section. is_namespace = _path_isdir(base_path) # Check for a file w/ a proper suffix exists. - for suffix, loader in self._loaders: + for suffix, loader_class in self._loaders: full_path = _path_join(self.path, tail_module + suffix) _verbose_message('trying {}'.format(full_path), verbosity=2) if cache_module + suffix in cache: if _path_isfile(full_path): - return (loader(fullname, full_path), []) + return self._get_spec(loader_class, fullname, full_path, None, target) if is_namespace: _verbose_message('possible namespace for {}'.format(base_path)) - return (None, [base_path]) - return (None, []) + spec = ModuleSpec(fullname, None) + spec.submodule_search_locations = [base_path] + return spec + return None def _fill_cache(self): """Fill the cache of potential modules and packages for this directory.""" @@ -1516,23 +2055,43 @@ def _resolve_name(name, package, level): return '{}.{}'.format(base, name) if name else base -def _find_module(name, path): +def _find_spec(name, path, target=None): """Find a module's loader.""" if not sys.meta_path: _warnings.warn('sys.meta_path is empty', ImportWarning) + # We check sys.modules here for the reload case. While a passed-in + # target will usually indicate a reload there is no guarantee, whereas + # sys.modules provides one. is_reload = name in sys.modules for finder in sys.meta_path: with _ImportLockContext(): - loader = finder.find_module(name, path) - if loader is not None: - # The parent import may have already imported this module. - if is_reload or name not in sys.modules: - return loader + try: + find_spec = finder.find_spec + except AttributeError: + loader = finder.find_module(name, path) + if loader is None: + continue + spec = spec_from_loader(name, loader) else: + spec = find_spec(name, path, target) + if spec is not None: + # The parent import may have already imported this module. + if not is_reload and name in sys.modules: + module = sys.modules[name] try: - return sys.modules[name].__loader__ + __spec__ = module.__spec__ except AttributeError: - return loader + # We use the found spec since that is the one that + # we would have used if the parent module hadn't + # beaten us to the punch. + return spec + else: + if __spec__ is None: + return spec + else: + return __spec__ + else: + return spec else: return None @@ -1566,54 +2125,28 @@ def _find_and_load_unlocked(name, import_): # Crazy side-effects! if name in sys.modules: return sys.modules[name] - # Backwards-compatibility; be nicer to skip the dict lookup. parent_module = sys.modules[parent] try: path = parent_module.__path__ except AttributeError: msg = (_ERR_MSG + '; {} is not a package').format(name, parent) raise ImportError(msg, name=name) - loader = _find_module(name, path) - if loader is None: + spec = _find_spec(name, path) + if spec is None: raise ImportError(_ERR_MSG.format(name), name=name) - elif name not in sys.modules: - # The parent import may have already imported this module. - loader.load_module(name) - _verbose_message('import {!r} # {!r}', name, loader) - # Backwards-compatibility; be nicer to skip the dict lookup. - module = sys.modules[name] + else: + module = _SpecMethods(spec)._load_unlocked() if parent: # Set the module as an attribute on its parent. parent_module = sys.modules[parent] setattr(parent_module, name.rpartition('.')[2], module) - # Set __package__ if the loader did not. - if getattr(module, '__package__', None) is None: - try: - module.__package__ = module.__name__ - if not hasattr(module, '__path__'): - module.__package__ = module.__package__.rpartition('.')[0] - except AttributeError: - pass - # Set loader if need be. - if getattr(module, '__loader__', None) is None: - try: - module.__loader__ = loader - except AttributeError: - pass return module def _find_and_load(name, import_): """Find and load the module, and release the import lock.""" - try: - lock = _get_module_lock(name) - finally: - _imp.release_lock() - lock.acquire() - try: + with _ModuleLockManager(name): return _find_and_load_unlocked(name, import_) - finally: - lock.release() def _gcd_import(name, package=None, level=0): @@ -1733,6 +2266,11 @@ def __import__(name, globals=None, locals=None, fromlist=(), level=0): return _handle_fromlist(module, fromlist, _gcd_import) +def _builtin_from_name(name): + spec = BuiltinImporter.find_spec(name) + methods = _SpecMethods(spec) + return methods._load_unlocked() + def _setup(sys_module, _imp_module): """Setup importlib by importing needed built-in modules and injecting them @@ -1751,23 +2289,30 @@ def _setup(sys_module, _imp_module): else: BYTECODE_SUFFIXES = DEBUG_BYTECODE_SUFFIXES + # Set up the spec for existing builtin/frozen modules. module_type = type(sys) for name, module in sys.modules.items(): if isinstance(module, module_type): - if getattr(module, '__loader__', None) is None: - if name in sys.builtin_module_names: - module.__loader__ = BuiltinImporter - elif _imp.is_frozen(name): - module.__loader__ = FrozenImporter + if name in sys.builtin_module_names: + loader = BuiltinImporter + elif _imp.is_frozen(name): + loader = FrozenImporter + else: + continue + spec = _spec_from_module(module, loader) + methods = _SpecMethods(spec) + methods.init_module_attrs(module) + # Directly load built-in modules needed during bootstrap. self_module = sys.modules[__name__] for builtin_name in ('_io', '_warnings', 'builtins', 'marshal'): if builtin_name not in sys.modules: - builtin_module = BuiltinImporter.load_module(builtin_name) + builtin_module = _builtin_from_name(builtin_name) else: builtin_module = sys.modules[builtin_name] setattr(self_module, builtin_name, builtin_module) + # Directly load the os module (needed during bootstrap). os_details = ('posix', ['/']), ('nt', ['\\', '/']) for builtin_os, path_separators in os_details: # Assumption made in _path_join() @@ -1778,29 +2323,33 @@ def _setup(sys_module, _imp_module): break else: try: - os_module = BuiltinImporter.load_module(builtin_os) + os_module = _builtin_from_name(builtin_os) break except ImportError: continue else: raise ImportError('importlib requires posix or nt') + setattr(self_module, '_os', os_module) + setattr(self_module, 'path_sep', path_sep) + setattr(self_module, 'path_separators', ''.join(path_separators)) + # Directly load the _thread module (needed during bootstrap). try: - thread_module = BuiltinImporter.load_module('_thread') + thread_module = _builtin_from_name('_thread') except ImportError: # Python was built without threads thread_module = None - weakref_module = BuiltinImporter.load_module('_weakref') + setattr(self_module, '_thread', thread_module) + + # Directly load the _weakref module (needed during bootstrap). + weakref_module = _builtin_from_name('_weakref') + setattr(self_module, '_weakref', weakref_module) + # Directly load the winreg module (needed during bootstrap). if builtin_os == 'nt': - winreg_module = BuiltinImporter.load_module('winreg') + winreg_module = _builtin_from_name('winreg') setattr(self_module, '_winreg', winreg_module) - setattr(self_module, '_os', os_module) - setattr(self_module, '_thread', thread_module) - setattr(self_module, '_weakref', weakref_module) - setattr(self_module, 'path_sep', path_sep) - setattr(self_module, 'path_separators', ''.join(path_separators)) # Constants setattr(self_module, '_relax_case', _make_relax_case()) EXTENSION_SUFFIXES.extend(_imp.extension_suffixes()) diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py index 01de67d..3995ff2 100644 --- a/Lib/importlib/abc.py +++ b/Lib/importlib/abc.py @@ -40,12 +40,18 @@ class MetaPathFinder(Finder): """Abstract base class for import finders on sys.meta_path.""" - @abc.abstractmethod + # We don't define find_spec() here since that would break + # hasattr checks we do to support backward compatibility. + + # XXX Deprecate def find_module(self, fullname, path): - """Abstract method which, when implemented, should find a module. - The fullname is a str and the path is a list of strings or None. - Returns a Loader object or None. + """Return a loader for the module. + + If no module is found, return None. The fullname is a str and + the path is a list of strings or None. + """ + return None def invalidate_caches(self): """An optional method for clearing the finder's cache, if any. @@ -60,17 +66,25 @@ class PathEntryFinder(Finder): """Abstract base class for path entry finders used by PathFinder.""" - @abc.abstractmethod + # We don't define find_spec() here since that would break + # hasattr checks we do to support backward compatibility. + + # XXX Deprecate. def find_loader(self, fullname): - """Abstract method which, when implemented, returns a module loader or - a possible part of a namespace. - The fullname is a str. Returns a 2-tuple of (Loader, portion) where - portion is a sequence of file system locations contributing to part of - a namespace package. The sequence may be empty and the loader may be - None. + """Return (loader, namespace portion) for the path entry. + + The fullname is a str. The namespace portion is a sequence of + path entries contributing to part of a namespace package. The + sequence may be empty. If loader is not None, the portion will + be ignored. + + The portion will be discarded if another path entry finder + locates the module as a normal module or package. + """ return None, [] + # XXX Deprecate. find_module = _bootstrap._find_module_shim def invalidate_caches(self): @@ -83,35 +97,47 @@ _register(PathEntryFinder, machinery.FileFinder) class Loader(metaclass=abc.ABCMeta): - """Abstract base class for import loaders. + """Abstract base class for import loaders.""" - The optional method module_repr(module) may be defined to provide a - repr for a module when appropriate (see PEP 420). The __repr__() method on - the module type will use the method as appropriate. + def create_module(self, spec): + """Return a module to initialize and into which to load. - """ + This method should raise ImportError if anything prevents it + from creating a new module. It may return None to indicate + that the spec should create the new module. - @abc.abstractmethod + create_module() is optional. + + """ + # By default, defer to _SpecMethods.create() for the new module. + return None + + # We don't define exec_module() here since that would break + # hasattr checks we do to support backward compatibility. + + # XXX Deprecate. def load_module(self, fullname): - """Abstract method which when implemented should load a module. - The fullname is a str. + """Return the loaded module. + + The module must be added to sys.modules and have import-related + attributes set properly. The fullname is a str. ImportError is raised on failure. + """ raise ImportError + # XXX Deprecate. def module_repr(self, module): """Return a module's repr. Used by the module type when the method does not raise NotImplementedError. + """ + # The exception will cause ModuleType.__repr__ to ignore this method. raise NotImplementedError - def init_module_attrs(self, module): - """Set the module's __loader__ attribute.""" - module.__loader__ = self - class ResourceLoader(Loader): @@ -138,12 +164,11 @@ class InspectLoader(Loader): """ - @abc.abstractmethod def is_package(self, fullname): - """Abstract method which when implemented should return whether the + """Optional method which when implemented should return whether the module is a package. The fullname is a str. Returns a bool. - Raises ImportError is the module cannot be found. + Raises ImportError if the module cannot be found. """ raise ImportError @@ -176,19 +201,10 @@ class InspectLoader(Loader): argument should be where the data was retrieved (when applicable).""" return compile(data, path, 'exec', dont_inherit=True) - def init_module_attrs(self, module): - """Initialize the __loader__ and __package__ attributes of the module. - - The name of the module is gleaned from module.__name__. The __package__ - attribute is set based on self.is_package(). - """ - super().init_module_attrs(module) - _bootstrap._init_package_attrs(self, module) - + exec_module = _bootstrap._LoaderBasics.exec_module load_module = _bootstrap._LoaderBasics.load_module -_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter, - _bootstrap.NamespaceLoader) +_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter) class ExecutionLoader(InspectLoader): @@ -225,18 +241,6 @@ class ExecutionLoader(InspectLoader): else: return self.source_to_code(source, path) - def init_module_attrs(self, module): - """Initialize the module's attributes. - - It is assumed that the module's name has been set on module.__name__. - It is also assumed that any path returned by self.get_filename() uses - (one of) the operating system's path separator(s) to separate filenames - from directories in order to set __path__ intelligently. - InspectLoader.init_module_attrs() sets __loader__ and __package__. - """ - super().init_module_attrs(module) - _bootstrap._init_file_attrs(self, module) - _register(ExecutionLoader, machinery.ExtensionFileLoader) diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py index ff826e4..2e1b2d7 100644 --- a/Lib/importlib/machinery.py +++ b/Lib/importlib/machinery.py @@ -5,6 +5,7 @@ import _imp from ._bootstrap import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES, OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES, EXTENSION_SUFFIXES) +from ._bootstrap import ModuleSpec from ._bootstrap import BuiltinImporter from ._bootstrap import FrozenImporter from ._bootstrap import WindowsRegistryFinder diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 7727f9d..04b1951 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -3,13 +3,14 @@ from ._bootstrap import MAGIC_NUMBER from ._bootstrap import cache_from_source from ._bootstrap import decode_source -from ._bootstrap import module_to_load -from ._bootstrap import set_loader -from ._bootstrap import set_package 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 contextlib import contextmanager import functools +import sys import warnings @@ -28,6 +29,58 @@ def resolve_name(name, package): return _resolve_name(name[level:], package, level) +@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 + + +# XXX deprecate +def set_package(fxn): + """Set __package__ on the returned module.""" + @functools.wraps(fxn) + def set_package_wrapper(*args, **kwargs): + 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 + + +# XXX deprecate +def set_loader(fxn): + """Set __loader__ on the returned module.""" + @functools.wraps(fxn) + def set_loader_wrapper(self, *args, **kwargs): + 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. @@ -46,13 +99,11 @@ def module_for_loader(fxn): the second argument. """ - warnings.warn('To make it easier for subclasses, please use ' - 'importlib.util.module_to_load() and ' - 'importlib.abc.Loader.init_module_attrs()', + warnings.warn('The import system now takes care of this automatically.', PendingDeprecationWarning, stacklevel=2) @functools.wraps(fxn) def module_for_loader_wrapper(self, fullname, *args, **kwargs): - with module_to_load(fullname) as module: + with _module_to_load(fullname) as module: module.__loader__ = self try: is_package = self.is_package(fullname) diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index 8167454..364b53f 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -245,14 +245,13 @@ def import_main_path(main_path): # We should not try to load __main__ # since that would execute 'if __name__ == "__main__"' # clauses, potentially causing a psuedo fork bomb. - loader = importlib.find_loader(main_name, path=dirs) main_module = types.ModuleType(main_name) - try: - loader.init_module_attrs(main_module) - except AttributeError: # init_module_attrs is optional - pass + # XXX Use a target of main_module? + spec = importlib.find_spec(main_name, path=dirs) + methods = importlib._bootstrap._SpecMethods(spec) + methods.init_module_attrs(main_module) main_module.__name__ = '__mp_main__' - code = loader.get_code(main_name) + code = spec.loader.get_code(main_name) exec(code, main_module.__dict__) old_main_modules.append(sys.modules['__main__']) diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py index fb9e56e..4682d22 100644 --- a/Lib/pkgutil.py +++ b/Lib/pkgutil.py @@ -430,6 +430,7 @@ def iter_importers(fullname=""): for item in path: yield get_importer(item) + def get_loader(module_or_name): """Get a PEP 302 "loader" object for module_or_name @@ -570,6 +571,7 @@ def extend_path(path, name): return path + def get_data(package, resource): """Get a resource from a package. @@ -592,10 +594,15 @@ def get_data(package, resource): which does not support get_data(), then None is returned. """ - loader = get_loader(package) + spec = importlib.find_spec(package) + if spec is None: + return None + loader = spec.loader if loader is None or not hasattr(loader, 'get_data'): return None - mod = sys.modules.get(package) or loader.load_module(package) + # XXX needs test + mod = (sys.modules.get(package) or + importlib._bootstrap._SpecMethods(spec).load()) if mod is None or not hasattr(mod, '__file__'): return None diff --git a/Lib/pydoc.py b/Lib/pydoc.py index d0240ff..e8127a2 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -167,8 +167,9 @@ def _split_list(s, predicate): def visiblename(name, all=None, obj=None): """Decide whether to show documentation on a variable.""" # Certain special names are redundant or internal. + # XXX Remove __initializing__? if name in {'__author__', '__builtins__', '__cached__', '__credits__', - '__date__', '__doc__', '__file__', '__initializing__', + '__date__', '__doc__', '__file__', '__spec__', '__loader__', '__module__', '__name__', '__package__', '__path__', '__qualname__', '__slots__', '__version__'}: return 0 diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 4c2aec3..5aa4b0a 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -2259,7 +2259,7 @@ order (MRO) for bases """ minstance.b = 2 minstance.a = 1 default_attributes = ['__name__', '__doc__', '__package__', - '__loader__'] + '__loader__', '__spec__'] names = [x for x in dir(minstance) if x not in default_attributes] self.assertEqual(names, ['a', 'b']) diff --git a/Lib/test/test_frozen.py b/Lib/test/test_frozen.py deleted file mode 100644 index 4c50cb7..0000000 --- a/Lib/test/test_frozen.py +++ /dev/null @@ -1,77 +0,0 @@ -# Test the frozen module defined in frozen.c. - -from test.support import captured_stdout, run_unittest -import unittest -import sys - -class FrozenTests(unittest.TestCase): - - module_attrs = frozenset(['__builtins__', '__cached__', '__doc__', - '__loader__', '__name__', - '__package__']) - package_attrs = frozenset(list(module_attrs) + ['__path__']) - - def test_frozen(self): - with captured_stdout() as stdout: - try: - import __hello__ - except ImportError as x: - self.fail("import __hello__ failed:" + str(x)) - self.assertEqual(__hello__.initialized, True) - expect = set(self.module_attrs) - expect.add('initialized') - self.assertEqual(set(dir(__hello__)), expect) - self.assertEqual(stdout.getvalue(), 'Hello world!\n') - - with captured_stdout() as stdout: - try: - import __phello__ - except ImportError as x: - self.fail("import __phello__ failed:" + str(x)) - self.assertEqual(__phello__.initialized, True) - expect = set(self.package_attrs) - expect.add('initialized') - if not "__phello__.spam" in sys.modules: - self.assertEqual(set(dir(__phello__)), expect) - else: - expect.add('spam') - self.assertEqual(set(dir(__phello__)), expect) - self.assertEqual(__phello__.__path__, []) - self.assertEqual(stdout.getvalue(), 'Hello world!\n') - - with captured_stdout() as stdout: - try: - import __phello__.spam - except ImportError as x: - self.fail("import __phello__.spam failed:" + str(x)) - self.assertEqual(__phello__.spam.initialized, True) - spam_expect = set(self.module_attrs) - spam_expect.add('initialized') - self.assertEqual(set(dir(__phello__.spam)), spam_expect) - phello_expect = set(self.package_attrs) - phello_expect.add('initialized') - phello_expect.add('spam') - self.assertEqual(set(dir(__phello__)), phello_expect) - self.assertEqual(stdout.getvalue(), 'Hello world!\n') - - try: - import __phello__.foo - except ImportError: - pass - else: - self.fail("import __phello__.foo should have failed") - - try: - import __phello__.foo - except ImportError: - pass - else: - self.fail("import __phello__.foo should have failed") - - del sys.modules['__hello__'] - del sys.modules['__phello__'] - del sys.modules['__phello__.spam'] - - -if __name__ == "__main__": - unittest.main() diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py index ae8e160..bda1541 100644 --- a/Lib/test/test_import.py +++ b/Lib/test/test_import.py @@ -1036,11 +1036,14 @@ class ImportTracebackTests(unittest.TestCase): # away from the traceback. self.create_module("foo", "") importlib = sys.modules['_frozen_importlib'] - old_load_module = importlib.SourceLoader.load_module + if 'load_module' in vars(importlib.SourceLoader): + old_exec_module = importlib.SourceLoader.exec_module + else: + old_exec_module = None try: - def load_module(*args): + def exec_module(*args): 1/0 - importlib.SourceLoader.load_module = load_module + importlib.SourceLoader.exec_module = exec_module try: import foo except ZeroDivisionError as e: @@ -1049,7 +1052,10 @@ class ImportTracebackTests(unittest.TestCase): self.fail("ZeroDivisionError should have been raised") self.assert_traceback(tb, [__file__, '<frozen importlib', __file__]) finally: - importlib.SourceLoader.load_module = old_load_module + if old_exec_module is None: + del importlib.SourceLoader.exec_module + else: + importlib.SourceLoader.exec_module = old_exec_module @unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE') def test_unencodable_filename(self): diff --git a/Lib/test/test_importlib/abc.py b/Lib/test/test_importlib/abc.py index b9ff344..2070dad 100644 --- a/Lib/test/test_importlib/abc.py +++ b/Lib/test/test_importlib/abc.py @@ -81,11 +81,6 @@ class LoaderTests(metaclass=abc.ABCMeta): pass @abc.abstractmethod - def test_module_reuse(self): - """If a module is already in sys.modules, it should be reused.""" - pass - - @abc.abstractmethod def test_state_after_failure(self): """If a module is already in sys.modules and a reload fails (e.g. a SyntaxError), the module should be in the state it was before diff --git a/Lib/test/test_importlib/builtin/test_finder.py b/Lib/test/test_importlib/builtin/test_finder.py index aa1ea42..934562f 100644 --- a/Lib/test/test_importlib/builtin/test_finder.py +++ b/Lib/test/test_importlib/builtin/test_finder.py @@ -8,6 +8,46 @@ import sys import unittest +class FindSpecTests(abc.FinderTests): + + """Test find_spec() for built-in modules.""" + + def test_module(self): + # Common case. + with util.uncache(builtin_util.NAME): + found = self.machinery.BuiltinImporter.find_spec(builtin_util.NAME) + self.assertTrue(found) + self.assertEqual(found.origin, 'built-in') + + # Built-in modules cannot be a package. + test_package = None + + # Built-in modules cannobt be in a package. + test_module_in_package = None + + # Built-in modules cannot be a package. + test_package_in_package = None + + # Built-in modules cannot be a package. + test_package_over_module = None + + def test_failure(self): + name = 'importlib' + assert name not in sys.builtin_module_names + spec = self.machinery.BuiltinImporter.find_spec(name) + self.assertIsNone(spec) + + def test_ignore_path(self): + # The value for 'path' should always trigger a failed import. + with util.uncache(builtin_util.NAME): + spec = self.machinery.BuiltinImporter.find_spec(builtin_util.NAME, + ['pkg']) + self.assertIsNone(spec) + +Frozen_FindSpecTests, Source_FindSpecTests = util.test_both(FindSpecTests, + machinery=[frozen_machinery, source_machinery]) + + class FinderTests(abc.FinderTests): """Test find_module() for built-in modules.""" @@ -17,22 +57,13 @@ class FinderTests(abc.FinderTests): with util.uncache(builtin_util.NAME): found = self.machinery.BuiltinImporter.find_module(builtin_util.NAME) self.assertTrue(found) + self.assertTrue(hasattr(found, 'load_module')) - def test_package(self): - # Built-in modules cannot be a package. - pass - - def test_module_in_package(self): - # Built-in modules cannobt be in a package. - pass - - def test_package_in_package(self): - # Built-in modules cannot be a package. - pass + # Built-in modules cannot be a package. + test_package = test_package_in_package = test_package_over_module = None - def test_package_over_module(self): - # Built-in modules cannot be a package. - pass + # Built-in modules cannot be in a package. + test_module_in_package = None def test_failure(self): assert 'importlib' not in sys.builtin_module_names diff --git a/Lib/test/test_importlib/builtin/test_loader.py b/Lib/test/test_importlib/builtin/test_loader.py index 4ee8dc4..200188f 100644 --- a/Lib/test/test_importlib/builtin/test_loader.py +++ b/Lib/test/test_importlib/builtin/test_loader.py @@ -9,6 +9,70 @@ import types import unittest +class ExecModTests(abc.LoaderTests): + + """Test exec_module() for built-in modules.""" + + @classmethod + def setUpClass(cls): + cls.verification = {'__name__': 'errno', '__package__': '', + '__loader__': cls.machinery.BuiltinImporter} + + def verify(self, module): + """Verify that the module matches against what it should have.""" + self.assertIsInstance(module, types.ModuleType) + for attr, value in self.verification.items(): + self.assertEqual(getattr(module, attr), value) + self.assertIn(module.__name__, sys.modules) + self.assertTrue(hasattr(module, '__spec__')) + self.assertEqual(module.__spec__.origin, 'built-in') + + def load_spec(self, name): + spec = self.machinery.ModuleSpec(name, self.machinery.BuiltinImporter, + origin='built-in') + module = types.ModuleType(name) + module.__spec__ = spec + self.machinery.BuiltinImporter.exec_module(module) + # Strictly not what exec_module() is supposed to do, but since + # _imp.init_builtin() does this we can't get around it. + return sys.modules[name] + + def test_module(self): + # Common case. + with util.uncache(builtin_util.NAME): + module = self.load_spec(builtin_util.NAME) + self.verify(module) + self.assertIn('built-in', str(module)) + + # Built-in modules cannot be a package. + test_package = None + + # Built-in modules cannot be a package. + test_lacking_parent = None + + # Not way to force an import failure. + test_state_after_failure = None + + def test_unloadable(self): + name = 'dssdsdfff' + assert name not in sys.builtin_module_names + with self.assertRaises(ImportError) as cm: + self.load_spec(name) + self.assertEqual(cm.exception.name, name) + + def test_already_imported(self): + # Using the name of a module already imported but not a built-in should + # still fail. + assert hasattr(unittest, '__file__') # Not a built-in. + with self.assertRaises(ImportError) as cm: + self.load_spec('unittest') + self.assertEqual(cm.exception.name, 'unittest') + + +Frozen_ExecModTests, Source_ExecModTests = util.test_both(ExecModTests, + machinery=[frozen_machinery, source_machinery]) + + class LoaderTests(abc.LoaderTests): """Test load_module() for built-in modules.""" @@ -33,17 +97,11 @@ class LoaderTests(abc.LoaderTests): module = self.load_module(builtin_util.NAME) self.verify(module) - def test_package(self): - # Built-in modules cannot be a package. - pass - - def test_lacking_parent(self): - # Built-in modules cannot be a package. - pass + # Built-in modules cannot be a package. + test_package = test_lacking_parent = None - def test_state_after_failure(self): - # Not way to force an imoprt failure. - pass + # No way to force an import failure. + test_state_after_failure = None def test_module_reuse(self): # Test that the same module is used in a reload. diff --git a/Lib/test/test_importlib/extension/test_case_sensitivity.py b/Lib/test/test_importlib/extension/test_case_sensitivity.py index bb74321..bb2528e 100644 --- a/Lib/test/test_importlib/extension/test_case_sensitivity.py +++ b/Lib/test/test_importlib/extension/test_case_sensitivity.py @@ -9,6 +9,8 @@ from . import util as ext_util frozen_machinery, source_machinery = util.import_importlib('importlib.machinery') +# XXX find_spec tests + @unittest.skipIf(ext_util.FILENAME is None, '_testcapi not available') @util.case_insensitive_tests class ExtensionModuleCaseSensitivityTest: diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py index 10e78cc..d0c8bb0 100644 --- a/Lib/test/test_importlib/extension/test_finder.py +++ b/Lib/test/test_importlib/extension/test_finder.py @@ -6,6 +6,7 @@ machinery = test_util.import_importlib('importlib.machinery') import unittest +# XXX find_spec tests class FinderTests(abc.FinderTests): @@ -20,21 +21,14 @@ class FinderTests(abc.FinderTests): def test_module(self): self.assertTrue(self.find_module(util.NAME)) - def test_package(self): - # No extension module as an __init__ available for testing. - pass + # No extension module as an __init__ available for testing. + test_package = test_package_in_package = None - def test_module_in_package(self): - # No extension module in a package available for testing. - pass + # No extension module in a package available for testing. + test_module_in_package = None - def test_package_in_package(self): - # No extension module as an __init__ available for testing. - pass - - def test_package_over_module(self): - # Extension modules cannot be an __init__ for a package. - pass + # Extension modules cannot be an __init__ for a package. + test_package_over_module = None def test_failure(self): self.assertIsNone(self.find_module('asdfjkl;')) diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py index 1e8afba..58bd09f 100644 --- a/Lib/test/test_importlib/extension/test_loader.py +++ b/Lib/test/test_importlib/extension/test_loader.py @@ -6,9 +6,75 @@ machinery = util.import_importlib('importlib.machinery') import os.path import sys +import types import unittest +class ExecModuleTests(abc.LoaderTests): + + """Test load_module() for extension modules.""" + + def setUp(self): + self.loader = self.machinery.ExtensionFileLoader(ext_util.NAME, + ext_util.FILEPATH) + + def exec_module(self, fullname): + module = types.ModuleType(fullname) + module.__spec__ = self.machinery.ModuleSpec(fullname, self.loader) + self.loader.exec_module(module) + return sys.modules[fullname] + + def test_exec_module_API(self): + with self.assertRaises(ImportError): + self.exec_module('XXX') + + + def test_module(self): + with util.uncache(ext_util.NAME): + module = self.exec_module(ext_util.NAME) + for attr, value in [('__name__', ext_util.NAME), + ('__file__', ext_util.FILEPATH), + ('__package__', '')]: + given = getattr(module, attr) + self.assertEqual(given, value, + '{}: {!r} != {!r}'.format(attr, given, value)) + self.assertIn(ext_util.NAME, sys.modules) + self.assertIsInstance(module.__loader__, + self.machinery.ExtensionFileLoader) + + # No extension module as __init__ available for testing. + test_package = None + + # No extension module in a package available for testing. + test_lacking_parent = None + + def test_module_reuse(self): + with util.uncache(ext_util.NAME): + module1 = self.exec_module(ext_util.NAME) + module2 = self.exec_module(ext_util.NAME) + self.assertIs(module1, module2) + + def test_state_after_failure(self): + # No easy way to trigger a failure after a successful import. + pass + + def test_unloadable(self): + name = 'asdfjkl;' + with self.assertRaises(ImportError) as cm: + self.exec_module(name) + self.assertEqual(cm.exception.name, name) + + def test_is_package(self): + self.assertFalse(self.loader.is_package(ext_util.NAME)) + for suffix in self.machinery.EXTENSION_SUFFIXES: + path = os.path.join('some', 'path', 'pkg', '__init__' + suffix) + loader = self.machinery.ExtensionFileLoader('pkg', path) + self.assertTrue(loader.is_package('pkg')) + +Frozen_ExecModuleTests, Source_ExecModuleTests = util.test_both( + ExecModuleTests, machinery=machinery) + + class LoaderTests(abc.LoaderTests): """Test load_module() for extension modules.""" @@ -39,13 +105,11 @@ class LoaderTests(abc.LoaderTests): self.assertIsInstance(module.__loader__, self.machinery.ExtensionFileLoader) - def test_package(self): - # No extension module as __init__ available for testing. - pass + # No extension module as __init__ available for testing. + test_package = None - def test_lacking_parent(self): - # No extension module in a package available for testing. - pass + # No extension module in a package available for testing. + test_lacking_parent = None def test_module_reuse(self): with util.uncache(ext_util.NAME): @@ -53,9 +117,8 @@ class LoaderTests(abc.LoaderTests): module2 = self.load_module(ext_util.NAME) self.assertIs(module1, module2) - def test_state_after_failure(self): - # No easy way to trigger a failure after a successful import. - pass + # No easy way to trigger a failure after a successful import. + test_state_after_failure = None def test_unloadable(self): name = 'asdfjkl;' diff --git a/Lib/test/test_importlib/frozen/test_finder.py b/Lib/test/test_importlib/frozen/test_finder.py index 9e629bf..f9f97f3 100644 --- a/Lib/test/test_importlib/frozen/test_finder.py +++ b/Lib/test/test_importlib/frozen/test_finder.py @@ -6,6 +6,41 @@ machinery = util.import_importlib('importlib.machinery') import unittest +class FindSpecTests(abc.FinderTests): + + """Test finding frozen modules.""" + + def find(self, name, path=None): + finder = self.machinery.FrozenImporter + return finder.find_spec(name, path) + + def test_module(self): + name = '__hello__' + spec = self.find(name) + self.assertEqual(spec.origin, 'frozen') + + def test_package(self): + spec = self.find('__phello__') + self.assertIsNotNone(spec) + + def test_module_in_package(self): + spec = self.find('__phello__.spam', ['__phello__']) + self.assertIsNotNone(spec) + + # No frozen package within another package to test with. + test_package_in_package = None + + # No easy way to test. + test_package_over_module = None + + def test_failure(self): + spec = self.find('<not real>') + self.assertIsNone(spec) + +Frozen_FindSpecTests, Source_FindSpecTests = util.test_both(FindSpecTests, + machinery=machinery) + + class FinderTests(abc.FinderTests): """Test finding frozen modules.""" @@ -27,13 +62,11 @@ class FinderTests(abc.FinderTests): loader = self.find('__phello__.spam', ['__phello__']) self.assertTrue(hasattr(loader, 'load_module')) - def test_package_in_package(self): - # No frozen package within another package to test with. - pass + # No frozen package within another package to test with. + test_package_in_package = None - def test_package_over_module(self): - # No easy way to test. - pass + # No easy way to test. + test_package_over_module = None def test_failure(self): loader = self.find('<not real>') diff --git a/Lib/test/test_importlib/frozen/test_loader.py b/Lib/test/test_importlib/frozen/test_loader.py index be8dc3c..2abbe92 100644 --- a/Lib/test/test_importlib/frozen/test_loader.py +++ b/Lib/test/test_importlib/frozen/test_loader.py @@ -3,9 +3,81 @@ from .. import util machinery = util.import_importlib('importlib.machinery') -import unittest + +import sys from test.support import captured_stdout import types +import unittest + + +class ExecModuleTests(abc.LoaderTests): + + def exec_module(self, name): + with util.uncache(name), captured_stdout() as stdout: + spec = self.machinery.ModuleSpec( + name, self.machinery.FrozenImporter, origin='frozen', + is_package=self.machinery.FrozenImporter.is_package(name)) + module = types.ModuleType(name) + module.__spec__ = spec + assert not hasattr(module, 'initialized') + self.machinery.FrozenImporter.exec_module(module) + self.assertTrue(module.initialized) + self.assertTrue(hasattr(module, '__spec__')) + self.assertEqual(module.__spec__.origin, 'frozen') + return module, stdout.getvalue() + + def test_module(self): + name = '__hello__' + module, output = self.exec_module(name) + check = {'__name__': name} + for attr, value in check.items(): + self.assertEqual(getattr(module, attr), value) + self.assertEqual(output, 'Hello world!\n') + self.assertTrue(hasattr(module, '__spec__')) + + def test_package(self): + name = '__phello__' + module, output = self.exec_module(name) + check = {'__name__': name} + for attr, value in check.items(): + attr_value = getattr(module, attr) + self.assertEqual(attr_value, value, + 'for {name}.{attr}, {given!r} != {expected!r}'.format( + name=name, attr=attr, given=attr_value, + expected=value)) + self.assertEqual(output, 'Hello world!\n') + + def test_lacking_parent(self): + name = '__phello__.spam' + with util.uncache('__phello__'): + module, output = self.exec_module(name) + check = {'__name__': name} + for attr, value in check.items(): + attr_value = getattr(module, attr) + self.assertEqual(attr_value, value, + 'for {name}.{attr}, {given} != {expected!r}'.format( + name=name, attr=attr, given=attr_value, + expected=value)) + self.assertEqual(output, 'Hello world!\n') + + + def test_module_repr(self): + name = '__hello__' + module, output = self.exec_module(name) + self.assertEqual(repr(module), + "<module '__hello__' (frozen)>") + + # No way to trigger an error in a frozen module. + test_state_after_failure = None + + def test_unloadable(self): + assert self.machinery.FrozenImporter.find_module('_not_real') is None + with self.assertRaises(ImportError) as cm: + self.exec_module('_not_real') + self.assertEqual(cm.exception.name, '_not_real') + +Frozen_ExecModuleTests, Source_ExecModuleTests = util.test_both(ExecModuleTests, + machinery=machinery) class LoaderTests(abc.LoaderTests): @@ -68,9 +140,8 @@ class LoaderTests(abc.LoaderTests): self.assertEqual(repr(module), "<module '__hello__' (frozen)>") - def test_state_after_failure(self): - # No way to trigger an error in a frozen module. - pass + # No way to trigger an error in a frozen module. + test_state_after_failure = None def test_unloadable(self): assert self.machinery.FrozenImporter.find_module('_not_real') is None diff --git a/Lib/test/test_importlib/import_/test_meta_path.py b/Lib/test/test_importlib/import_/test_meta_path.py index 74afd1b..a56253d 100644 --- a/Lib/test/test_importlib/import_/test_meta_path.py +++ b/Lib/test/test_importlib/import_/test_meta_path.py @@ -46,8 +46,8 @@ class CallingOrder: with util.import_state(meta_path=[]): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') - self.assertIsNone(importlib._bootstrap._find_module('nothing', - None)) + self.assertIsNone(importlib._bootstrap._find_spec('nothing', + None)) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, ImportWarning)) diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py index 979a481..ba8d605 100644 --- a/Lib/test/test_importlib/test_abc.py +++ b/Lib/test/test_importlib/test_abc.py @@ -284,22 +284,6 @@ class ExecutionLoaderDefaultsTests: tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests) Frozen_ELDefaultTests, Source_ELDefaultsTests = tests -##### Loader concrete methods ################################################## -class LoaderConcreteMethodTests: - - def test_init_module_attrs(self): - loader = self.LoaderSubclass() - module = types.ModuleType('blah') - loader.init_module_attrs(module) - self.assertEqual(module.__loader__, loader) - - -class Frozen_LoaderConcreateMethodTests(LoaderConcreteMethodTests, unittest.TestCase): - LoaderSubclass = Frozen_L - -class Source_LoaderConcreateMethodTests(LoaderConcreteMethodTests, unittest.TestCase): - LoaderSubclass = Source_L - ##### InspectLoader concrete methods ########################################### class InspectLoaderSourceToCodeTests: @@ -385,60 +369,6 @@ class Source_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase): InspectLoaderSubclass = Source_IL -class InspectLoaderInitModuleTests: - - def mock_is_package(self, return_value): - return mock.patch.object(self.InspectLoaderSubclass, 'is_package', - return_value=return_value) - - def init_module_attrs(self, name): - loader = self.InspectLoaderSubclass() - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertEqual(module.__loader__, loader) - return module - - def test_package(self): - # If a package, then __package__ == __name__, __path__ == [] - with self.mock_is_package(True): - name = 'blah' - module = self.init_module_attrs(name) - self.assertEqual(module.__package__, name) - self.assertEqual(module.__path__, []) - - def test_toplevel(self): - # If a module is top-level, __package__ == '' - with self.mock_is_package(False): - name = 'blah' - module = self.init_module_attrs(name) - self.assertEqual(module.__package__, '') - - def test_submodule(self): - # If a module is contained within a package then set __package__ to the - # package name. - with self.mock_is_package(False): - name = 'pkg.mod' - module = self.init_module_attrs(name) - self.assertEqual(module.__package__, 'pkg') - - def test_is_package_ImportError(self): - # If is_package() raises ImportError, __package__ should be None and - # __path__ should not be set. - with self.mock_is_package(False) as mocked_method: - mocked_method.side_effect = ImportError - name = 'mod' - module = self.init_module_attrs(name) - self.assertIsNone(module.__package__) - self.assertFalse(hasattr(module, '__path__')) - - -class Frozen_ILInitModuleTests(InspectLoaderInitModuleTests, unittest.TestCase): - InspectLoaderSubclass = Frozen_IL - -class Source_ILInitModuleTests(InspectLoaderInitModuleTests, unittest.TestCase): - InspectLoaderSubclass = Source_IL - - class InspectLoaderLoadModuleTests: """Test InspectLoader.load_module().""" @@ -550,80 +480,6 @@ class Source_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase): ExecutionLoaderSubclass = Source_EL -class ExecutionLoaderInitModuleTests: - - def mock_is_package(self, return_value): - return mock.patch.object(self.ExecutionLoaderSubclass, 'is_package', - return_value=return_value) - - @contextlib.contextmanager - def mock_methods(self, is_package, filename): - is_package_manager = self.mock_is_package(is_package) - get_filename_manager = mock.patch.object(self.ExecutionLoaderSubclass, - 'get_filename', return_value=filename) - with is_package_manager as mock_is_package: - with get_filename_manager as mock_get_filename: - yield {'is_package': mock_is_package, - 'get_filename': mock_get_filename} - - def test_toplevel(self): - # Verify __loader__, __file__, and __package__; no __path__. - name = 'blah' - path = os.path.join('some', 'path', '{}.py'.format(name)) - with self.mock_methods(False, path): - loader = self.ExecutionLoaderSubclass() - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertIs(module.__loader__, loader) - self.assertEqual(module.__file__, path) - self.assertEqual(module.__package__, '') - self.assertFalse(hasattr(module, '__path__')) - - def test_package(self): - # Verify __loader__, __file__, __package__, and __path__. - name = 'pkg' - path = os.path.join('some', 'pkg', '__init__.py') - with self.mock_methods(True, path): - loader = self.ExecutionLoaderSubclass() - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertIs(module.__loader__, loader) - self.assertEqual(module.__file__, path) - self.assertEqual(module.__package__, 'pkg') - self.assertEqual(module.__path__, [os.path.dirname(path)]) - - def test_submodule(self): - # Verify __package__ and not __path__; test_toplevel() takes care of - # other attributes. - name = 'pkg.submodule' - path = os.path.join('some', 'pkg', 'submodule.py') - with self.mock_methods(False, path): - loader = self.ExecutionLoaderSubclass() - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertEqual(module.__package__, 'pkg') - self.assertEqual(module.__file__, path) - self.assertFalse(hasattr(module, '__path__')) - - def test_get_filename_ImportError(self): - # If get_filename() raises ImportError, don't set __file__. - name = 'blah' - path = 'blah.py' - with self.mock_methods(False, path) as mocked_methods: - mocked_methods['get_filename'].side_effect = ImportError - loader = self.ExecutionLoaderSubclass() - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertFalse(hasattr(module, '__file__')) - - -class Frozen_ELInitModuleTests(ExecutionLoaderInitModuleTests, unittest.TestCase): - ExecutionLoaderSubclass = Frozen_EL - -class Source_ELInitModuleTests(ExecutionLoaderInitModuleTests, unittest.TestCase): - ExecutionLoaderSubclass = Source_EL - - ##### SourceLoader concrete methods ############################################ class SourceLoader: @@ -952,58 +808,5 @@ class Source_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.Test SourceOnlyLoaderMock = Source_SourceOnlyL -class SourceLoaderInitModuleAttrTests: - - """Tests for importlib.abc.SourceLoader.init_module_attrs().""" - - def test_init_module_attrs(self): - # If __file__ set, __cached__ == importlib.util.cached_from_source(__file__). - name = 'blah' - path = 'blah.py' - loader = self.SourceOnlyLoaderMock(path) - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertEqual(module.__loader__, loader) - self.assertEqual(module.__package__, '') - self.assertEqual(module.__file__, path) - self.assertEqual(module.__cached__, self.util.cache_from_source(path)) - - def test_no_get_filename(self): - # No __file__, no __cached__. - with mock.patch.object(self.SourceOnlyLoaderMock, 'get_filename') as mocked: - mocked.side_effect = ImportError - name = 'blah' - loader = self.SourceOnlyLoaderMock('blah.py') - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertFalse(hasattr(module, '__file__')) - self.assertFalse(hasattr(module, '__cached__')) - - -class Frozen_SLInitModuleAttrTests(SourceLoaderInitModuleAttrTests, unittest.TestCase): - SourceOnlyLoaderMock = Frozen_SourceOnlyL - util = frozen_util - - # Difficult to test under source thanks to cross-module mocking needs. - @mock.patch('importlib._bootstrap.cache_from_source') - def test_cache_from_source_NotImplementedError(self, mock_cache_from_source): - # If importlib.util.cache_from_source() raises NotImplementedError don't set - # __cached__. - mock_cache_from_source.side_effect = NotImplementedError - name = 'blah' - path = 'blah.py' - loader = self.SourceOnlyLoaderMock(path) - module = types.ModuleType(name) - loader.init_module_attrs(module) - self.assertEqual(module.__file__, path) - self.assertFalse(hasattr(module, '__cached__')) - - -class Source_SLInitModuleAttrTests(SourceLoaderInitModuleAttrTests, unittest.TestCase): - SourceOnlyLoaderMock = Source_SourceOnlyL - util = source_util - - - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py index 5126634..c6c2d47 100644 --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -165,6 +165,96 @@ class Source_FindLoaderTests(FindLoaderTests, unittest.TestCase): init = source_init +class FindSpecTests: + + class FakeMetaFinder: + @staticmethod + def find_spec(name, path=None, target=None): return name, path, target + + def test_sys_modules(self): + name = 'some_mod' + with util.uncache(name): + module = types.ModuleType(name) + loader = 'a loader!' + spec = self.machinery.ModuleSpec(name, loader) + module.__loader__ = loader + module.__spec__ = spec + sys.modules[name] = module + found = self.init.find_spec(name) + self.assertEqual(found, spec) + + def test_sys_modules_without___loader__(self): + name = 'some_mod' + with util.uncache(name): + module = types.ModuleType(name) + del module.__loader__ + loader = 'a loader!' + spec = self.machinery.ModuleSpec(name, loader) + module.__spec__ = spec + sys.modules[name] = module + found = self.init.find_spec(name) + self.assertEqual(found, spec) + + def test_sys_modules_spec_is_None(self): + name = 'some_mod' + with util.uncache(name): + module = types.ModuleType(name) + module.__spec__ = None + sys.modules[name] = module + with self.assertRaises(ValueError): + self.init.find_spec(name) + + def test_sys_modules_loader_is_None(self): + name = 'some_mod' + with util.uncache(name): + module = types.ModuleType(name) + spec = self.machinery.ModuleSpec(name, None) + module.__spec__ = spec + sys.modules[name] = module + found = self.init.find_spec(name) + self.assertEqual(found, spec) + + def test_sys_modules_spec_is_not_set(self): + name = 'some_mod' + with util.uncache(name): + module = types.ModuleType(name) + try: + del module.__spec__ + except AttributeError: + pass + sys.modules[name] = module + with self.assertRaises(ValueError): + self.init.find_spec(name) + + def test_success(self): + name = 'some_mod' + with util.uncache(name): + with util.import_state(meta_path=[self.FakeMetaFinder]): + self.assertEqual((name, None, None), + self.init.find_spec(name)) + + def test_success_path(self): + # Searching on a path should work. + name = 'some_mod' + path = 'path to some place' + with util.uncache(name): + with util.import_state(meta_path=[self.FakeMetaFinder]): + self.assertEqual((name, path, None), + self.init.find_spec(name, path)) + + def test_nothing(self): + # None is returned upon failure to find a loader. + self.assertIsNone(self.init.find_spec('nevergoingtofindthismodule')) + +class Frozen_FindSpecTests(FindSpecTests, unittest.TestCase): + init = frozen_init + machinery = frozen_machinery + +class Source_FindSpecTests(FindSpecTests, unittest.TestCase): + init = source_init + machinery = source_machinery + + class ReloadTests: """Test module reloading for builtin and extension modules.""" @@ -219,6 +309,7 @@ class ReloadTests: with support.temp_cwd(None) as cwd: with util.uncache('spam'): with support.DirsOnSysPath(cwd): + # Start as a plain module. self.init.invalidate_caches() path = os.path.join(cwd, name + '.py') cached = self.util.cache_from_source(path) @@ -232,11 +323,14 @@ class ReloadTests: support.create_empty_file(path) module = self.init.import_module(name) ns = vars(module) - del ns['__initializing__'] loader = ns.pop('__loader__') + spec = ns.pop('__spec__') + self.assertEqual(spec.name, name) + self.assertEqual(spec.loader, loader) self.assertEqual(loader.path, path) self.assertEqual(ns, expected) + # Change to a package. self.init.invalidate_caches() init_path = os.path.join(cwd, name, '__init__.py') cached = self.util.cache_from_source(init_path) @@ -252,18 +346,21 @@ class ReloadTests: os.rename(path, init_path) reloaded = self.init.reload(module) ns = vars(reloaded) - del ns['__initializing__'] loader = ns.pop('__loader__') + spec = ns.pop('__spec__') + self.assertEqual(spec.name, name) + self.assertEqual(spec.loader, loader) self.assertIs(reloaded, module) self.assertEqual(loader.path, init_path) + self.maxDiff = None self.assertEqual(ns, expected) def test_reload_namespace_changed(self): - self.maxDiff = None name = 'spam' with support.temp_cwd(None) as cwd: with util.uncache('spam'): with support.DirsOnSysPath(cwd): + # Start as a namespace package. self.init.invalidate_caches() bad_path = os.path.join(cwd, name, '__init.py') cached = self.util.cache_from_source(bad_path) @@ -276,9 +373,12 @@ class ReloadTests: init_file.write('eggs = None') module = self.init.import_module(name) ns = vars(module) - del ns['__initializing__'] loader = ns.pop('__loader__') path = ns.pop('__path__') + spec = ns.pop('__spec__') + self.assertEqual(spec.name, name) + self.assertIs(spec.loader, None) + self.assertIsNot(loader, None) self.assertEqual(set(path), set([os.path.dirname(bad_path)])) with self.assertRaises(AttributeError): @@ -286,6 +386,7 @@ class ReloadTests: loader.path self.assertEqual(ns, expected) + # Change to a regular package. self.init.invalidate_caches() init_path = os.path.join(cwd, name, '__init__.py') cached = self.util.cache_from_source(init_path) @@ -301,8 +402,10 @@ class ReloadTests: os.rename(bad_path, init_path) reloaded = self.init.reload(module) ns = vars(reloaded) - del ns['__initializing__'] loader = ns.pop('__loader__') + spec = ns.pop('__spec__') + self.assertEqual(spec.name, name) + self.assertEqual(spec.loader, loader) self.assertIs(reloaded, module) self.assertEqual(loader.path, init_path) self.assertEqual(ns, expected) @@ -371,12 +474,23 @@ class StartupTests: # Issue #17098: all modules should have __loader__ defined. for name, module in sys.modules.items(): if isinstance(module, types.ModuleType): - self.assertTrue(hasattr(module, '__loader__'), - '{!r} lacks a __loader__ attribute'.format(name)) - if self.machinery.BuiltinImporter.find_module(name): - self.assertIsNot(module.__loader__, None) - elif self.machinery.FrozenImporter.find_module(name): - self.assertIsNot(module.__loader__, None) + with self.subTest(name=name): + self.assertTrue(hasattr(module, '__loader__'), + '{!r} lacks a __loader__ attribute'.format(name)) + if self.machinery.BuiltinImporter.find_module(name): + self.assertIsNot(module.__loader__, None) + elif self.machinery.FrozenImporter.find_module(name): + self.assertIsNot(module.__loader__, None) + + def test_everyone_has___spec__(self): + for name, module in sys.modules.items(): + if isinstance(module, types.ModuleType): + with self.subTest(name=name): + self.assertTrue(hasattr(module, '__spec__')) + if self.machinery.BuiltinImporter.find_module(name): + self.assertIsNot(module.__spec__, None) + elif self.machinery.FrozenImporter.find_module(name): + self.assertIsNot(module.__spec__, None) class Frozen_StartupTests(StartupTests, unittest.TestCase): machinery = frozen_machinery diff --git a/Lib/test/test_importlib/test_spec.py b/Lib/test/test_importlib/test_spec.py new file mode 100644 index 0000000..e4ef39f --- /dev/null +++ b/Lib/test/test_importlib/test_spec.py @@ -0,0 +1,968 @@ +from . import util + +frozen_init, source_init = util.import_importlib('importlib') +frozen_bootstrap = frozen_init._bootstrap +source_bootstrap = source_init._bootstrap +frozen_machinery, source_machinery = util.import_importlib('importlib.machinery') +frozen_util, source_util = util.import_importlib('importlib.util') + +import os.path +from test.support import CleanImport +import unittest +import sys + + + +class TestLoader: + + def __init__(self, path=None, is_package=None): +# if path: +# if is_package: +# if not path.endswith('.py'): +# path = os.path.join(path, '__init__.py') +# elif is_package is None: +# is_package = path.endswith('__init__.py') + + self.path = path + self.package = is_package + + def __repr__(self): + return '<TestLoader object>' + + def __getattr__(self, name): + if name == 'get_filename' and self.path is not None: + return self._get_filename + if name == 'is_package': + return self._is_package + raise AttributeError(name) + + def _get_filename(self, name): + return self.path + + def _is_package(self, name): + return self.package + + +class NewLoader(TestLoader): + + EGGS = 1 + + def exec_module(self, module): + module.eggs = self.EGGS + + +class LegacyLoader(TestLoader): + + HAM = -1 + + @frozen_util.module_for_loader + def load_module(self, module): + module.ham = self.HAM + return module + + +class ModuleSpecTests: + + def setUp(self): + self.name = 'spam' + self.path = 'spam.py' + self.cached = self.util.cache_from_source(self.path) + self.loader = TestLoader() + self.spec = self.machinery.ModuleSpec(self.name, self.loader) + self.loc_spec = self.machinery.ModuleSpec(self.name, self.loader, + origin=self.path) + self.loc_spec._set_fileattr = True + + def test_default(self): + spec = self.machinery.ModuleSpec(self.name, self.loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_default_no_loader(self): + spec = self.machinery.ModuleSpec(self.name, None) + + self.assertEqual(spec.name, self.name) + self.assertIs(spec.loader, None) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_default_is_package_false(self): + spec = self.machinery.ModuleSpec(self.name, self.loader, + is_package=False) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_default_is_package_true(self): + spec = self.machinery.ModuleSpec(self.name, self.loader, + is_package=True) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, []) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_equality(self): + other = type(sys.implementation)(name=self.name, + loader=self.loader, + origin=None, + submodule_search_locations=None, + has_location=False, + cached=None, + ) + + self.assertTrue(self.spec == other) + + def test_equality_location(self): + other = type(sys.implementation)(name=self.name, + loader=self.loader, + origin=self.path, + submodule_search_locations=None, + has_location=True, + cached=self.cached, + ) + + self.assertEqual(self.loc_spec, other) + + def test_inequality(self): + other = type(sys.implementation)(name='ham', + loader=self.loader, + origin=None, + submodule_search_locations=None, + has_location=False, + cached=None, + ) + + self.assertNotEqual(self.spec, other) + + def test_inequality_incomplete(self): + other = type(sys.implementation)(name=self.name, + loader=self.loader, + ) + + self.assertNotEqual(self.spec, other) + + def test_package(self): + spec = self.machinery.ModuleSpec('spam.eggs', self.loader) + + self.assertEqual(spec.parent, 'spam') + + def test_package_is_package(self): + spec = self.machinery.ModuleSpec('spam.eggs', self.loader, + is_package=True) + + self.assertEqual(spec.parent, 'spam.eggs') + + # cached + + def test_cached_set(self): + before = self.spec.cached + self.spec.cached = 'there' + after = self.spec.cached + + self.assertIs(before, None) + self.assertEqual(after, 'there') + + def test_cached_no_origin(self): + spec = self.machinery.ModuleSpec(self.name, self.loader) + + self.assertIs(spec.cached, None) + + def test_cached_with_origin_not_location(self): + spec = self.machinery.ModuleSpec(self.name, self.loader, + origin=self.path) + + self.assertIs(spec.cached, None) + + def test_cached_source(self): + expected = self.util.cache_from_source(self.path) + + self.assertEqual(self.loc_spec.cached, expected) + + def test_cached_source_unknown_suffix(self): + self.loc_spec.origin = 'spam.spamspamspam' + + self.assertIs(self.loc_spec.cached, None) + + def test_cached_source_missing_cache_tag(self): + original = sys.implementation.cache_tag + sys.implementation.cache_tag = None + try: + cached = self.loc_spec.cached + finally: + sys.implementation.cache_tag = original + + self.assertIs(cached, None) + + def test_cached_sourceless(self): + self.loc_spec.origin = 'spam.pyc' + + self.assertEqual(self.loc_spec.cached, 'spam.pyc') + + +class Frozen_ModuleSpecTests(ModuleSpecTests, unittest.TestCase): + util = frozen_util + machinery = frozen_machinery + + +class Source_ModuleSpecTests(ModuleSpecTests, unittest.TestCase): + util = source_util + machinery = source_machinery + + +class ModuleSpecMethodsTests: + + def setUp(self): + self.name = 'spam' + self.path = 'spam.py' + self.cached = self.util.cache_from_source(self.path) + self.loader = TestLoader() + self.spec = self.machinery.ModuleSpec(self.name, self.loader) + self.loc_spec = self.machinery.ModuleSpec(self.name, self.loader, + origin=self.path) + self.loc_spec._set_fileattr = True + + # init_module_attrs + + def test_init_module_attrs(self): + module = type(sys)(self.name) + spec = self.machinery.ModuleSpec(self.name, self.loader) + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertEqual(module.__name__, spec.name) + self.assertIs(module.__loader__, spec.loader) + self.assertEqual(module.__package__, spec.parent) + self.assertIs(module.__spec__, spec) + self.assertFalse(hasattr(module, '__path__')) + self.assertFalse(hasattr(module, '__file__')) + self.assertFalse(hasattr(module, '__cached__')) + + def test_init_module_attrs_package(self): + module = type(sys)(self.name) + spec = self.machinery.ModuleSpec(self.name, self.loader) + spec.submodule_search_locations = ['spam', 'ham'] + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertEqual(module.__name__, spec.name) + self.assertIs(module.__loader__, spec.loader) + self.assertEqual(module.__package__, spec.parent) + self.assertIs(module.__spec__, spec) + self.assertIs(module.__path__, spec.submodule_search_locations) + self.assertFalse(hasattr(module, '__file__')) + self.assertFalse(hasattr(module, '__cached__')) + + def test_init_module_attrs_location(self): + module = type(sys)(self.name) + spec = self.loc_spec + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertEqual(module.__name__, spec.name) + self.assertIs(module.__loader__, spec.loader) + self.assertEqual(module.__package__, spec.parent) + self.assertIs(module.__spec__, spec) + self.assertFalse(hasattr(module, '__path__')) + self.assertEqual(module.__file__, spec.origin) + self.assertEqual(module.__cached__, + self.util.cache_from_source(spec.origin)) + + def test_init_module_attrs_different_name(self): + module = type(sys)('eggs') + spec = self.machinery.ModuleSpec(self.name, self.loader) + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertEqual(module.__name__, spec.name) + + def test_init_module_attrs_different_spec(self): + module = type(sys)(self.name) + module.__spec__ = self.machinery.ModuleSpec('eggs', object()) + spec = self.machinery.ModuleSpec(self.name, self.loader) + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertEqual(module.__name__, spec.name) + self.assertIs(module.__loader__, spec.loader) + self.assertEqual(module.__package__, spec.parent) + self.assertIs(module.__spec__, spec) + + def test_init_module_attrs_already_set(self): + module = type(sys)('ham.eggs') + module.__loader__ = object() + module.__package__ = 'ham' + module.__path__ = ['eggs'] + module.__file__ = 'ham/eggs/__init__.py' + module.__cached__ = self.util.cache_from_source(module.__file__) + original = vars(module).copy() + spec = self.loc_spec + spec.submodule_search_locations = [''] + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertIs(module.__loader__, original['__loader__']) + self.assertEqual(module.__package__, original['__package__']) + self.assertIs(module.__path__, original['__path__']) + self.assertEqual(module.__file__, original['__file__']) + self.assertEqual(module.__cached__, original['__cached__']) + + def test_init_module_attrs_immutable(self): + module = object() + spec = self.loc_spec + spec.submodule_search_locations = [''] + self.bootstrap._SpecMethods(spec).init_module_attrs(module) + + self.assertFalse(hasattr(module, '__name__')) + self.assertFalse(hasattr(module, '__loader__')) + self.assertFalse(hasattr(module, '__package__')) + self.assertFalse(hasattr(module, '__spec__')) + self.assertFalse(hasattr(module, '__path__')) + self.assertFalse(hasattr(module, '__file__')) + self.assertFalse(hasattr(module, '__cached__')) + + # create() + + def test_create(self): + created = self.bootstrap._SpecMethods(self.spec).create() + + self.assertEqual(created.__name__, self.spec.name) + self.assertIs(created.__loader__, self.spec.loader) + self.assertEqual(created.__package__, self.spec.parent) + self.assertIs(created.__spec__, self.spec) + self.assertFalse(hasattr(created, '__path__')) + self.assertFalse(hasattr(created, '__file__')) + self.assertFalse(hasattr(created, '__cached__')) + + def test_create_from_loader(self): + module = type(sys.implementation)() + class CreatingLoader(TestLoader): + def create_module(self, spec): + return module + self.spec.loader = CreatingLoader() + created = self.bootstrap._SpecMethods(self.spec).create() + + self.assertIs(created, module) + self.assertEqual(created.__name__, self.spec.name) + self.assertIs(created.__loader__, self.spec.loader) + self.assertEqual(created.__package__, self.spec.parent) + self.assertIs(created.__spec__, self.spec) + self.assertFalse(hasattr(created, '__path__')) + self.assertFalse(hasattr(created, '__file__')) + self.assertFalse(hasattr(created, '__cached__')) + + def test_create_from_loader_not_handled(self): + class CreatingLoader(TestLoader): + def create_module(self, spec): + return None + self.spec.loader = CreatingLoader() + created = self.bootstrap._SpecMethods(self.spec).create() + + self.assertEqual(created.__name__, self.spec.name) + self.assertIs(created.__loader__, self.spec.loader) + self.assertEqual(created.__package__, self.spec.parent) + self.assertIs(created.__spec__, self.spec) + self.assertFalse(hasattr(created, '__path__')) + self.assertFalse(hasattr(created, '__file__')) + self.assertFalse(hasattr(created, '__cached__')) + + # exec() + + def test_exec(self): + self.spec.loader = NewLoader() + module = self.bootstrap._SpecMethods(self.spec).create() + sys.modules[self.name] = module + self.assertFalse(hasattr(module, 'eggs')) + self.bootstrap._SpecMethods(self.spec).exec(module) + + self.assertEqual(module.eggs, 1) + + # load() + + def test_load(self): + self.spec.loader = NewLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + installed = sys.modules[self.spec.name] + + self.assertEqual(loaded.eggs, 1) + self.assertIs(loaded, installed) + + def test_load_replaced(self): + replacement = object() + class ReplacingLoader(TestLoader): + def exec_module(self, module): + sys.modules[module.__name__] = replacement + self.spec.loader = ReplacingLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + installed = sys.modules[self.spec.name] + + self.assertIs(loaded, replacement) + self.assertIs(installed, replacement) + + def test_load_failed(self): + class FailedLoader(TestLoader): + def exec_module(self, module): + raise RuntimeError + self.spec.loader = FailedLoader() + with CleanImport(self.spec.name): + with self.assertRaises(RuntimeError): + loaded = self.bootstrap._SpecMethods(self.spec).load() + self.assertNotIn(self.spec.name, sys.modules) + + def test_load_failed_removed(self): + class FailedLoader(TestLoader): + def exec_module(self, module): + del sys.modules[module.__name__] + raise RuntimeError + self.spec.loader = FailedLoader() + with CleanImport(self.spec.name): + with self.assertRaises(RuntimeError): + loaded = self.bootstrap._SpecMethods(self.spec).load() + self.assertNotIn(self.spec.name, sys.modules) + + def test_load_existing(self): + existing = type(sys)('ham') + existing.count = 5 + self.spec.loader = NewLoader() + with CleanImport(self.name): + sys.modules[self.name] = existing + assert self.spec.name == self.name + loaded = self.bootstrap._SpecMethods(self.spec).load() + + self.assertEqual(loaded.eggs, 1) + self.assertFalse(hasattr(loaded, 'ham')) + + def test_load_legacy(self): + self.spec.loader = LegacyLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + + self.assertEqual(loaded.ham, -1) + + def test_load_legacy_attributes(self): + self.spec.loader = LegacyLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + + self.assertIs(loaded.__loader__, self.spec.loader) + self.assertEqual(loaded.__package__, self.spec.parent) + self.assertIs(loaded.__spec__, self.spec) + + def test_load_legacy_attributes_immutable(self): + module = object() + class ImmutableLoader(TestLoader): + def load_module(self, name): + sys.modules[name] = module + return module + self.spec.loader = ImmutableLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + + self.assertIs(sys.modules[self.spec.name], module) + + # reload() + + def test_reload(self): + self.spec.loader = NewLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded) + installed = sys.modules[self.spec.name] + + self.assertEqual(loaded.eggs, 1) + self.assertIs(reloaded, loaded) + self.assertIs(installed, loaded) + + def test_reload_modified(self): + self.spec.loader = NewLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + loaded.eggs = 2 + reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded) + + self.assertEqual(loaded.eggs, 1) + self.assertIs(reloaded, loaded) + + def test_reload_extra_attributes(self): + self.spec.loader = NewLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + loaded.available = False + reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded) + + self.assertFalse(loaded.available) + self.assertIs(reloaded, loaded) + + def test_reload_init_module_attrs(self): + self.spec.loader = NewLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + loaded.__name__ = 'ham' + del loaded.__loader__ + del loaded.__package__ + del loaded.__spec__ + self.bootstrap._SpecMethods(self.spec).exec(loaded) + + self.assertEqual(loaded.__name__, self.spec.name) + self.assertIs(loaded.__loader__, self.spec.loader) + self.assertEqual(loaded.__package__, self.spec.parent) + self.assertIs(loaded.__spec__, self.spec) + self.assertFalse(hasattr(loaded, '__path__')) + self.assertFalse(hasattr(loaded, '__file__')) + self.assertFalse(hasattr(loaded, '__cached__')) + + def test_reload_legacy(self): + self.spec.loader = LegacyLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._SpecMethods(self.spec).load() + reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded) + installed = sys.modules[self.spec.name] + + self.assertEqual(loaded.ham, -1) + self.assertIs(reloaded, loaded) + self.assertIs(installed, loaded) + + +class Frozen_ModuleSpecMethodsTests(ModuleSpecMethodsTests, unittest.TestCase): + bootstrap = frozen_bootstrap + machinery = frozen_machinery + util = frozen_util + + +class Source_ModuleSpecMethodsTests(ModuleSpecMethodsTests, unittest.TestCase): + bootstrap = source_bootstrap + machinery = source_machinery + util = source_util + + +class ModuleReprTests: + + # XXX Add more tests for repr(module) once ModuleSpec._module_repr() + # is in place? + + def setUp(self): + self.module = type(os)('spam') + self.spec = self.machinery.ModuleSpec('spam', TestLoader()) + + def test_module___loader___module_repr(self): + class Loader: + def module_repr(self, module): + return '<delicious {}>'.format(module.__name__) + self.module.__loader__ = Loader() + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, '<delicious spam>') + + def test_module___loader___module_repr_bad(self): + class Loader(TestLoader): + def module_repr(self, module): + raise Exception + self.module.__loader__ = Loader() + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, + '<module {!r} (<TestLoader object>)>'.format('spam')) + + def test_module___spec__(self): + origin = 'in a hole, in the ground' + self.spec.origin = origin + self.module.__spec__ = self.spec + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, '<module {!r} ({})>'.format('spam', origin)) + + def test_module___spec___location(self): + location = 'in_a_galaxy_far_far_away.py' + self.spec.origin = location + self.spec._set_fileattr = True + self.module.__spec__ = self.spec + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, + '<module {!r} from {!r}>'.format('spam', location)) + + def test_module___spec___no_origin(self): + self.spec.loader = TestLoader() + self.module.__spec__ = self.spec + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, + '<module {!r} (<TestLoader object>)>'.format('spam')) + + def test_module___spec___no_origin_no_loader(self): + self.spec.loader = None + self.module.__spec__ = self.spec + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, '<module {!r}>'.format('spam')) + + def test_module_no_name(self): + del self.module.__name__ + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, '<module {!r}>'.format('?')) + + def test_module_with_file(self): + filename = 'e/i/e/i/o/spam.py' + self.module.__file__ = filename + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, + '<module {!r} from {!r}>'.format('spam', filename)) + + def test_module_no_file(self): + self.module.__loader__ = TestLoader() + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, + '<module {!r} (<TestLoader object>)>'.format('spam')) + + def test_module_no_file_no_loader(self): + modrepr = self.bootstrap._module_repr(self.module) + + self.assertEqual(modrepr, '<module {!r}>'.format('spam')) + + +class Frozen_ModuleReprTests(ModuleReprTests, unittest.TestCase): + bootstrap = frozen_bootstrap + machinery = frozen_machinery + util = frozen_util + + +class Source_ModuleReprTests(ModuleReprTests, unittest.TestCase): + bootstrap = source_bootstrap + machinery = source_machinery + util = source_util + + +class FactoryTests: + + def setUp(self): + self.name = 'spam' + self.path = 'spam.py' + self.cached = self.util.cache_from_source(self.path) + self.loader = TestLoader() + self.fileloader = TestLoader(self.path) + self.pkgloader = TestLoader(self.path, True) + + # spec_from_loader() + + def test_spec_from_loader_default(self): + spec = self.util.spec_from_loader(self.name, self.loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_default_with_bad_is_package(self): + class Loader: + def is_package(self, name): + raise ImportError + loader = Loader() + spec = self.util.spec_from_loader(self.name, loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_origin(self): + origin = 'somewhere over the rainbow' + spec = self.util.spec_from_loader(self.name, self.loader, + origin=origin) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, origin) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_is_package_false(self): + spec = self.util.spec_from_loader(self.name, self.loader, + is_package=False) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_is_package_true(self): + spec = self.util.spec_from_loader(self.name, self.loader, + is_package=True) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, []) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_origin_and_is_package(self): + origin = 'where the streets have no name' + spec = self.util.spec_from_loader(self.name, self.loader, + origin=origin, is_package=True) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertIs(spec.origin, origin) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, []) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_is_package_with_loader_false(self): + loader = TestLoader(is_package=False) + spec = self.util.spec_from_loader(self.name, loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_is_package_with_loader_true(self): + loader = TestLoader(is_package=True) + spec = self.util.spec_from_loader(self.name, loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, loader) + self.assertIs(spec.origin, None) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, []) + self.assertIs(spec.cached, None) + self.assertFalse(spec.has_location) + + def test_spec_from_loader_default_with_file_loader(self): + spec = self.util.spec_from_loader(self.name, self.fileloader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_loader_is_package_false_with_fileloader(self): + spec = self.util.spec_from_loader(self.name, self.fileloader, + is_package=False) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_loader_is_package_true_with_fileloader(self): + spec = self.util.spec_from_loader(self.name, self.fileloader, + is_package=True) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, ['']) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + # spec_from_file_location() + + def test_spec_from_file_location_default(self): + if self.machinery is source_machinery: + raise unittest.SkipTest('not sure why this is breaking...') + spec = self.util.spec_from_file_location(self.name, self.path) + + self.assertEqual(spec.name, self.name) + self.assertIsInstance(spec.loader, + self.machinery.SourceFileLoader) + self.assertEqual(spec.loader.name, self.name) + self.assertEqual(spec.loader.path, self.path) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_default_without_location(self): + spec = self.util.spec_from_file_location(self.name) + + self.assertIs(spec, None) + + def test_spec_from_file_location_default_bad_suffix(self): + spec = self.util.spec_from_file_location(self.name, 'spam.eggs') + + self.assertIs(spec, None) + + def test_spec_from_file_location_loader_no_location(self): + spec = self.util.spec_from_file_location(self.name, + loader=self.fileloader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_loader_no_location_no_get_filename(self): + spec = self.util.spec_from_file_location(self.name, + loader=self.loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.loader) + self.assertEqual(spec.origin, '<unknown>') + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_loader_no_location_bad_get_filename(self): + class Loader: + def get_filename(self, name): + raise ImportError + loader = Loader() + spec = self.util.spec_from_file_location(self.name, loader=loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, loader) + self.assertEqual(spec.origin, '<unknown>') + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertIs(spec.cached, None) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_none(self): + spec = self.util.spec_from_file_location(self.name, self.path, + loader=self.fileloader, + submodule_search_locations=None) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_empty(self): + spec = self.util.spec_from_file_location(self.name, self.path, + loader=self.fileloader, + submodule_search_locations=[]) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, ['']) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_not_empty(self): + spec = self.util.spec_from_file_location(self.name, self.path, + loader=self.fileloader, + submodule_search_locations=['eggs']) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, ['eggs']) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_default(self): + spec = self.util.spec_from_file_location(self.name, self.path, + loader=self.pkgloader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.pkgloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertEqual(spec.submodule_search_locations, ['']) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_default_not_package(self): + class Loader: + def is_package(self, name): + return False + loader = Loader() + spec = self.util.spec_from_file_location(self.name, self.path, + loader=loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, loader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_default_no_is_package(self): + spec = self.util.spec_from_file_location(self.name, self.path, + loader=self.fileloader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, self.fileloader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + def test_spec_from_file_location_smsl_default_bad_is_package(self): + class Loader: + def is_package(self, name): + raise ImportError + loader = Loader() + spec = self.util.spec_from_file_location(self.name, self.path, + loader=loader) + + self.assertEqual(spec.name, self.name) + self.assertEqual(spec.loader, loader) + self.assertEqual(spec.origin, self.path) + self.assertIs(spec.loader_state, None) + self.assertIs(spec.submodule_search_locations, None) + self.assertEqual(spec.cached, self.cached) + self.assertTrue(spec.has_location) + + +class Frozen_FactoryTests(FactoryTests, unittest.TestCase): + util = frozen_util + machinery = frozen_machinery + + +class Source_FactoryTests(FactoryTests, unittest.TestCase): + util = source_util + machinery = source_machinery diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 2ac57df..604e44d 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -34,71 +34,6 @@ Frozen_DecodeSourceBytesTests, Source_DecodeSourceBytesTests = test_util.test_bo DecodeSourceBytesTests, util=[frozen_util, source_util]) -class ModuleToLoadTests: - - module_name = 'ModuleManagerTest_module' - - def setUp(self): - support.unload(self.module_name) - self.addCleanup(support.unload, self.module_name) - - def test_new_module(self): - # Test a new module is created, inserted into sys.modules, has - # __initializing__ set to True after entering the context manager, - # and __initializing__ set to False after exiting. - with self.util.module_to_load(self.module_name) as module: - self.assertIn(self.module_name, sys.modules) - self.assertIs(sys.modules[self.module_name], module) - self.assertTrue(module.__initializing__) - self.assertFalse(module.__initializing__) - - def test_new_module_failed(self): - # Test the module is removed from sys.modules. - try: - with self.util.module_to_load(self.module_name) as module: - self.assertIn(self.module_name, sys.modules) - raise exception - except Exception: - self.assertNotIn(self.module_name, sys.modules) - else: - self.fail('importlib.util.module_to_load swallowed an exception') - - def test_reload(self): - # Test that the same module is in sys.modules. - created_module = types.ModuleType(self.module_name) - sys.modules[self.module_name] = created_module - with self.util.module_to_load(self.module_name) as module: - self.assertIs(module, created_module) - - def test_reload_failed(self): - # Test that the module was left in sys.modules. - created_module = types.ModuleType(self.module_name) - sys.modules[self.module_name] = created_module - try: - with self.util.module_to_load(self.module_name) as module: - raise Exception - except Exception: - self.assertIn(self.module_name, sys.modules) - else: - self.fail('importlib.util.module_to_load swallowed an exception') - - def test_reset_name(self): - # If reset_name is true then module.__name__ = name, else leave it be. - odd_name = 'not your typical name' - created_module = types.ModuleType(self.module_name) - created_module.__name__ = odd_name - sys.modules[self.module_name] = created_module - with self.util.module_to_load(self.module_name) as module: - self.assertEqual(module.__name__, self.module_name) - created_module.__name__ = odd_name - with self.util.module_to_load(self.module_name, reset_name=False) as module: - self.assertEqual(module.__name__, odd_name) - -Frozen_ModuleToLoadTests, Source_ModuleToLoadTests = test_util.test_both( - ModuleToLoadTests, - util=[frozen_util, source_util]) - - class ModuleForLoaderTests: """Tests for importlib.util.module_for_loader.""" diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py index 5a9b503..536f7cf 100644 --- a/Lib/test/test_module.py +++ b/Lib/test/test_module.py @@ -37,8 +37,10 @@ class ModuleTests(unittest.TestCase): self.assertEqual(foo.__doc__, None) self.assertIs(foo.__loader__, None) self.assertIs(foo.__package__, None) + self.assertIs(foo.__spec__, None) self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": None, - "__loader__": None, "__package__": None}) + "__loader__": None, "__package__": None, + "__spec__": None}) def test_ascii_docstring(self): # ASCII docstring @@ -47,7 +49,8 @@ class ModuleTests(unittest.TestCase): self.assertEqual(foo.__doc__, "foodoc") self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": "foodoc", - "__loader__": None, "__package__": None}) + "__loader__": None, "__package__": None, + "__spec__": None}) def test_unicode_docstring(self): # Unicode docstring @@ -56,7 +59,8 @@ class ModuleTests(unittest.TestCase): self.assertEqual(foo.__doc__, "foodoc\u1234") self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": "foodoc\u1234", - "__loader__": None, "__package__": None}) + "__loader__": None, "__package__": None, + "__spec__": None}) def test_reinit(self): # Reinitialization should not replace the __dict__ @@ -69,7 +73,7 @@ class ModuleTests(unittest.TestCase): self.assertEqual(foo.bar, 42) self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": "foodoc", "bar": 42, - "__loader__": None, "__package__": None}) + "__loader__": None, "__package__": None, "__spec__": None}) self.assertTrue(foo.__dict__ is d) def test_dont_clear_dict(self): diff --git a/Lib/test/test_namespace_pkgs.py b/Lib/test/test_namespace_pkgs.py index 4570bee..6639612 100644 --- a/Lib/test/test_namespace_pkgs.py +++ b/Lib/test/test_namespace_pkgs.py @@ -1,5 +1,4 @@ import contextlib -from importlib._bootstrap import NamespaceLoader import importlib.abc import importlib.machinery import os @@ -290,24 +289,5 @@ class ModuleAndNamespacePackageInSameDir(NamespacePackageTest): self.assertEqual(a_test.attr, 'in module') -class ABCTests(unittest.TestCase): - - def setUp(self): - self.loader = NamespaceLoader('foo', ['pkg'], - importlib.machinery.PathFinder) - - def test_is_package(self): - self.assertTrue(self.loader.is_package('foo')) - - def test_get_code(self): - self.assertTrue(isinstance(self.loader.get_code('foo'), types.CodeType)) - - def test_get_source(self): - self.assertEqual(self.loader.get_source('foo'), '') - - def test_abc_isinstance(self): - self.assertTrue(isinstance(self.loader, importlib.abc.InspectLoader)) - - if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_pkg.py b/Lib/test/test_pkg.py index 355efe7..9883000 100644 --- a/Lib/test/test_pkg.py +++ b/Lib/test/test_pkg.py @@ -199,14 +199,14 @@ class TestPkg(unittest.TestCase): import t5 self.assertEqual(fixdir(dir(t5)), ['__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', '__path__', 'foo', - 'string', 't5']) + '__name__', '__package__', '__path__', '__spec__', + 'foo', 'string', 't5']) self.assertEqual(fixdir(dir(t5.foo)), ['__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', 'string']) + '__name__', '__package__', '__spec__', 'string']) self.assertEqual(fixdir(dir(t5.string)), ['__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', 'spam']) + '__name__', '__package__', '__spec__', 'spam']) def test_6(self): hier = [ @@ -222,14 +222,15 @@ class TestPkg(unittest.TestCase): import t6 self.assertEqual(fixdir(dir(t6)), ['__all__', '__cached__', '__doc__', '__file__', - '__loader__', '__name__', '__package__', '__path__']) + '__loader__', '__name__', '__package__', '__path__', + '__spec__']) s = """ import t6 from t6 import * self.assertEqual(fixdir(dir(t6)), ['__all__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', - '__path__', 'eggs', 'ham', 'spam']) + '__path__', '__spec__', 'eggs', 'ham', 'spam']) self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6']) """ self.run_code(s) @@ -256,18 +257,19 @@ class TestPkg(unittest.TestCase): import t7 as tas self.assertEqual(fixdir(dir(tas)), ['__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', '__path__']) + '__name__', '__package__', '__path__', '__spec__']) self.assertFalse(t7) from t7 import sub as subpar self.assertEqual(fixdir(dir(subpar)), ['__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', '__path__']) + '__name__', '__package__', '__path__', '__spec__']) self.assertFalse(t7) self.assertFalse(sub) from t7.sub import subsub as subsubsub self.assertEqual(fixdir(dir(subsubsub)), ['__cached__', '__doc__', '__file__', '__loader__', - '__name__', '__package__', '__path__', 'spam']) + '__name__', '__package__', '__path__', '__spec__', + 'spam']) self.assertFalse(t7) self.assertFalse(sub) self.assertFalse(subsub) diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index 1f48853..52dd0bc 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -208,9 +208,16 @@ class ExtendPathTests(unittest.TestCase): importers = list(iter_importers(fullname)) expected_importer = get_importer(pathitem) for finder in importers: + loader = finder.find_module(fullname) + try: + loader = loader.loader + except AttributeError: + # For now we still allow raw loaders from + # find_module(). + pass self.assertIsInstance(finder, importlib.machinery.FileFinder) self.assertEqual(finder, expected_importer) - self.assertIsInstance(finder.find_module(fullname), + self.assertIsInstance(loader, importlib.machinery.SourceFileLoader) self.assertIsNone(finder.find_module(pkgname)) @@ -222,8 +229,11 @@ class ExtendPathTests(unittest.TestCase): finally: shutil.rmtree(dirname) del sys.path[0] - del sys.modules['spam'] - del sys.modules['spam.eggs'] + try: + del sys.modules['spam'] + del sys.modules['spam.eggs'] + except KeyError: + pass def test_mixed_namespace(self): diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 104e3b5..a504bde 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -253,6 +253,7 @@ class LongReprTest(unittest.TestCase): print("cached_path_len =", cached_path_len) def test_module(self): + self.maxDiff = None self._check_path_limitations(self.pkgname) create_empty_file(os.path.join(self.subpkgname, self.pkgname + '.py')) importlib.invalidate_caches() diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 16e2e12..f19c4ab 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -47,6 +47,7 @@ implicit_namespace = { "__cached__": None, "__package__": None, "__doc__": None, +# "__spec__": None, # XXX Uncomment. } example_namespace = { "sys": sys, @@ -56,7 +57,7 @@ example_namespace = { "run_name_in_sys_modules": False, "module_in_sys_modules": False, "nested": dict(implicit_namespace, - x=1, __name__="<run>", __loader__=None), + x=1, __name__="<run>", __loader__=None, __spec__=None), } example_namespace.update(implicit_namespace) @@ -243,6 +244,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin): "__name__": mod_name, "__file__": mod_fname, "__package__": mod_name.rpartition(".")[0], +# "__spec__": None, # XXX Needs to be set. }) if alter_sys: expected_ns.update({ @@ -279,6 +281,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin): "__name__": mod_name, "__file__": mod_fname, "__package__": pkg_name, +# "__spec__": None, # XXX Needs to be set. }) if alter_sys: expected_ns.update({ |