mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue 19944: Fix importlib.find_spec() so it imports parents as needed.
The function is also moved to importlib.util.
This commit is contained in:
parent
128ee220e2
commit
6029e08691
11 changed files with 292 additions and 230 deletions
|
@ -89,22 +89,6 @@ Functions
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
Parent packages are automatically imported.
|
Parent packages are automatically imported.
|
||||||
|
|
||||||
.. function:: find_spec(name, path=None)
|
|
||||||
|
|
||||||
Find the :term:`spec <module spec>` for a module, optionally within the
|
|
||||||
specified *path*. If the module is in :attr:`sys.modules`, then
|
|
||||||
``sys.modules[name].__spec__`` is returned (unless the spec would be
|
|
||||||
``None`` or is not set, in which case :exc:`ValueError` is raised).
|
|
||||||
Otherwise a search using :attr:`sys.meta_path` is done. ``None`` is
|
|
||||||
returned if no spec is found.
|
|
||||||
|
|
||||||
A dotted name does not have its parent implicitly imported as that requires
|
|
||||||
loading them and that may not be desired. To properly import a submodule you
|
|
||||||
will need to import all parent packages of the submodule and use the correct
|
|
||||||
argument to *path*.
|
|
||||||
|
|
||||||
.. versionadded:: 3.4
|
|
||||||
|
|
||||||
.. function:: find_loader(name, path=None)
|
.. function:: find_loader(name, path=None)
|
||||||
|
|
||||||
Find the loader for a module, optionally within the specified *path*. If the
|
Find the loader for a module, optionally within the specified *path*. If the
|
||||||
|
@ -125,7 +109,7 @@ Functions
|
||||||
attribute is set to ``None``.
|
attribute is set to ``None``.
|
||||||
|
|
||||||
.. deprecated:: 3.4
|
.. deprecated:: 3.4
|
||||||
Use :func:`find_spec` instead.
|
Use :func:`importlib.util.find_spec` instead.
|
||||||
|
|
||||||
.. function:: invalidate_caches()
|
.. function:: invalidate_caches()
|
||||||
|
|
||||||
|
@ -1111,6 +1095,22 @@ an :term:`importer`.
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
|
.. function:: find_spec(name, package=None)
|
||||||
|
|
||||||
|
Find the :term:`spec <module spec>` for a module, optionally relative to
|
||||||
|
the specified **package** name. If the module is in :attr:`sys.modules`,
|
||||||
|
then ``sys.modules[name].__spec__`` is returned (unless the spec would be
|
||||||
|
``None`` or is not set, in which case :exc:`ValueError` is raised).
|
||||||
|
Otherwise a search using :attr:`sys.meta_path` is done. ``None`` is
|
||||||
|
returned if no spec is found.
|
||||||
|
|
||||||
|
If **name** is for a submodule (contains a dot), the parent module is
|
||||||
|
automatically imported.
|
||||||
|
|
||||||
|
**name** and **package** work the same as for :func:`import_module`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
.. decorator:: module_for_loader
|
.. decorator:: module_for_loader
|
||||||
|
|
||||||
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
|
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import importlib
|
import importlib
|
||||||
import importlib.abc
|
import importlib.abc
|
||||||
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
from platform import python_version
|
from platform import python_version
|
||||||
import re
|
import re
|
||||||
|
@ -660,7 +661,7 @@ class EditorWindow(object):
|
||||||
return
|
return
|
||||||
# XXX Ought to insert current file's directory in front of path
|
# XXX Ought to insert current file's directory in front of path
|
||||||
try:
|
try:
|
||||||
spec = importlib.find_spec(name)
|
spec = importlib.util.find_spec(name)
|
||||||
except (ValueError, ImportError) as msg:
|
except (ValueError, ImportError) as msg:
|
||||||
tkMessageBox.showerror("Import error", str(msg), parent=self.text)
|
tkMessageBox.showerror("Import error", str(msg), parent=self.text)
|
||||||
return
|
return
|
||||||
|
|
|
@ -11,8 +11,6 @@ __all__ = ['__import__', 'import_module', 'invalidate_caches', 'reload']
|
||||||
# initialised below if the frozen one is not available).
|
# initialised below if the frozen one is not available).
|
||||||
import _imp # Just the builtin component, NOT the full Python module
|
import _imp # Just the builtin component, NOT the full Python module
|
||||||
import sys
|
import sys
|
||||||
import types
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import _frozen_importlib as _bootstrap
|
import _frozen_importlib as _bootstrap
|
||||||
|
@ -34,6 +32,10 @@ _r_long = _bootstrap._r_long
|
||||||
# Fully bootstrapped at this point, import whatever you like, circular
|
# Fully bootstrapped at this point, import whatever you like, circular
|
||||||
# dependencies and startup overhead minimisation permitting :)
|
# dependencies and startup overhead minimisation permitting :)
|
||||||
|
|
||||||
|
import types
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
# Public API #########################################################
|
# Public API #########################################################
|
||||||
|
|
||||||
from ._bootstrap import __import__
|
from ._bootstrap import __import__
|
||||||
|
@ -47,47 +49,16 @@ def invalidate_caches():
|
||||||
finder.invalidate_caches()
|
finder.invalidate_caches()
|
||||||
|
|
||||||
|
|
||||||
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].__spec__ is returned. If that happens to be
|
|
||||||
set to None, then ValueError is raised. If the module is not in
|
|
||||||
sys.modules, then sys.meta_path is searched for a suitable spec with the
|
|
||||||
value of 'path' given to the finders. None is returned if no spec could
|
|
||||||
be found.
|
|
||||||
|
|
||||||
Dotted names do not have their parent packages implicitly imported. You will
|
|
||||||
most likely need to explicitly import all parent packages in the proper
|
|
||||||
order for a submodule to get the correct spec.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if name not in sys.modules:
|
|
||||||
return _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
|
|
||||||
|
|
||||||
|
|
||||||
def find_loader(name, path=None):
|
def find_loader(name, path=None):
|
||||||
"""Return the loader for the specified module.
|
"""Return the loader for the specified module.
|
||||||
|
|
||||||
This is a backward-compatible wrapper around find_spec().
|
This is a backward-compatible wrapper around find_spec().
|
||||||
|
|
||||||
This function is deprecated in favor of importlib.find_spec().
|
This function is deprecated in favor of importlib.util.find_spec().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
warnings.warn('Use importlib.find_spec() instead.', DeprecationWarning,
|
warnings.warn('Use importlib.util.find_spec() instead.',
|
||||||
stacklevel=2)
|
DeprecationWarning, stacklevel=2)
|
||||||
try:
|
try:
|
||||||
loader = sys.modules[name].__loader__
|
loader = sys.modules[name].__loader__
|
||||||
if loader is None:
|
if loader is None:
|
||||||
|
@ -167,7 +138,8 @@ def reload(module):
|
||||||
pkgpath = parent.__path__
|
pkgpath = parent.__path__
|
||||||
else:
|
else:
|
||||||
pkgpath = None
|
pkgpath = None
|
||||||
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, module)
|
target = module
|
||||||
|
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, target)
|
||||||
methods = _bootstrap._SpecMethods(spec)
|
methods = _bootstrap._SpecMethods(spec)
|
||||||
methods.exec(module)
|
methods.exec(module)
|
||||||
# The module may have replaced itself in sys.modules!
|
# The module may have replaced itself in sys.modules!
|
||||||
|
|
|
@ -7,6 +7,7 @@ from ._bootstrap import source_from_cache
|
||||||
from ._bootstrap import spec_from_loader
|
from ._bootstrap import spec_from_loader
|
||||||
from ._bootstrap import spec_from_file_location
|
from ._bootstrap import spec_from_file_location
|
||||||
from ._bootstrap import _resolve_name
|
from ._bootstrap import _resolve_name
|
||||||
|
from ._bootstrap import _find_spec
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import functools
|
import functools
|
||||||
|
@ -29,6 +30,77 @@ def resolve_name(name, package):
|
||||||
return _resolve_name(name[level:], package, level)
|
return _resolve_name(name[level:], package, level)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_spec_from_path(name, path=None):
|
||||||
|
"""Return the spec for the specified module.
|
||||||
|
|
||||||
|
First, sys.modules is checked to see if the module was already imported. If
|
||||||
|
so, then sys.modules[name].__spec__ is returned. If that happens to be
|
||||||
|
set to None, then ValueError is raised. If the module is not in
|
||||||
|
sys.modules, then sys.meta_path is searched for a suitable spec with the
|
||||||
|
value of 'path' given to the finders. None is returned if no spec could
|
||||||
|
be found.
|
||||||
|
|
||||||
|
Dotted names do not have their parent packages implicitly imported. You will
|
||||||
|
most likely need to explicitly import all parent packages in the proper
|
||||||
|
order for a submodule to get the correct spec.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if name not in sys.modules:
|
||||||
|
return _find_spec(name, path)
|
||||||
|
else:
|
||||||
|
module = sys.modules[name]
|
||||||
|
if module is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
spec = module.__spec__
|
||||||
|
except AttributeError:
|
||||||
|
raise ValueError('{}.__spec__ is not set'.format(name))
|
||||||
|
else:
|
||||||
|
if spec is None:
|
||||||
|
raise ValueError('{}.__spec__ is None'.format(name))
|
||||||
|
return spec
|
||||||
|
|
||||||
|
|
||||||
|
def find_spec(name, package=None):
|
||||||
|
"""Return the spec for the specified module.
|
||||||
|
|
||||||
|
First, sys.modules is checked to see if the module was already imported. If
|
||||||
|
so, then sys.modules[name].__spec__ is returned. If that happens to be
|
||||||
|
set to None, then ValueError is raised. If the module is not in
|
||||||
|
sys.modules, then sys.meta_path is searched for a suitable spec with the
|
||||||
|
value of 'path' given to the finders. None is returned if no spec could
|
||||||
|
be found.
|
||||||
|
|
||||||
|
If the name is for submodule (contains a dot), the parent module is
|
||||||
|
automatically imported.
|
||||||
|
|
||||||
|
The name and package arguments work the same as importlib.import_module().
|
||||||
|
In other words, relative module names (with leading dots) work.
|
||||||
|
|
||||||
|
"""
|
||||||
|
fullname = resolve_name(name, package) if name.startswith('.') else name
|
||||||
|
if fullname not in sys.modules:
|
||||||
|
parent_name = fullname.rpartition('.')[0]
|
||||||
|
if parent_name:
|
||||||
|
# Use builtins.__import__() in case someone replaced it.
|
||||||
|
parent = __import__(parent_name, fromlist=['__path__'])
|
||||||
|
return _find_spec(fullname, parent.__path__)
|
||||||
|
else:
|
||||||
|
return _find_spec(fullname, None)
|
||||||
|
else:
|
||||||
|
module = sys.modules[fullname]
|
||||||
|
if module is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
spec = module.__spec__
|
||||||
|
except AttributeError:
|
||||||
|
raise ValueError('{}.__spec__ is not set'.format(name))
|
||||||
|
else:
|
||||||
|
if spec is None:
|
||||||
|
raise ValueError('{}.__spec__ is None'.format(name))
|
||||||
|
return spec
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _module_to_load(name):
|
def _module_to_load(name):
|
||||||
is_reload = name in sys.modules
|
is_reload = name in sys.modules
|
||||||
|
|
|
@ -611,7 +611,7 @@ def get_data(package, resource):
|
||||||
which does not support get_data(), then None is returned.
|
which does not support get_data(), then None is returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
spec = importlib.find_spec(package)
|
spec = importlib.util.find_spec(package)
|
||||||
if spec is None:
|
if spec is None:
|
||||||
return None
|
return None
|
||||||
loader = spec.loader
|
loader = spec.loader
|
||||||
|
|
|
@ -42,7 +42,7 @@ Instances of this class have the following instance variables:
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib.util
|
||||||
import tokenize
|
import tokenize
|
||||||
from token import NAME, DEDENT, OP
|
from token import NAME, DEDENT, OP
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
@ -141,7 +141,7 @@ def _readmodule(module, path, inpackage=None):
|
||||||
else:
|
else:
|
||||||
search_path = path + sys.path
|
search_path = path + sys.path
|
||||||
# XXX This will change once issue19944 lands.
|
# XXX This will change once issue19944 lands.
|
||||||
spec = importlib.find_spec(fullmodule, search_path)
|
spec = importlib.util._find_spec_from_path(fullmodule, search_path)
|
||||||
fname = spec.loader.get_filename(fullmodule)
|
fname = spec.loader.get_filename(fullmodule)
|
||||||
_modules[fullmodule] = dict
|
_modules[fullmodule] = dict
|
||||||
if spec.loader.is_package(fullmodule):
|
if spec.loader.is_package(fullmodule):
|
||||||
|
|
26
Lib/runpy.py
26
Lib/runpy.py
|
@ -13,9 +13,8 @@ importers when locating support scripts as well as when importing modules.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import importlib.machinery # importlib first so we can test #15386 via -m
|
import importlib.machinery # importlib first so we can test #15386 via -m
|
||||||
|
import importlib.util
|
||||||
import types
|
import types
|
||||||
from importlib import find_spec
|
|
||||||
from importlib.util import spec_from_loader
|
|
||||||
from pkgutil import read_code, get_importer
|
from pkgutil import read_code, get_importer
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -100,33 +99,16 @@ def _run_module_code(code, init_globals=None,
|
||||||
# may be cleared when the temporary module goes away
|
# may be cleared when the temporary module goes away
|
||||||
return mod_globals.copy()
|
return mod_globals.copy()
|
||||||
|
|
||||||
|
# Helper to get the loader, code and filename for a module
|
||||||
def _fixed_find_spec(mod_name):
|
def _get_module_details(mod_name):
|
||||||
# find_spec has the same annoying behaviour as find_loader did (it
|
|
||||||
# fails to work properly for dotted names), so this is a fixed version
|
|
||||||
# ala pkgutil.get_loader
|
|
||||||
if mod_name.startswith('.'):
|
|
||||||
msg = "Relative module name {!r} not supported".format(mod_name)
|
|
||||||
raise ImportError(msg)
|
|
||||||
path = None
|
|
||||||
pkg_name = mod_name.rpartition(".")[0]
|
|
||||||
if pkg_name:
|
|
||||||
pkg = importlib.import_module(pkg_name)
|
|
||||||
path = getattr(pkg, "__path__", None)
|
|
||||||
if path is None:
|
|
||||||
return None
|
|
||||||
try:
|
try:
|
||||||
return importlib.find_spec(mod_name, path)
|
spec = importlib.util.find_spec(mod_name)
|
||||||
except (ImportError, AttributeError, TypeError, ValueError) as ex:
|
except (ImportError, AttributeError, TypeError, ValueError) as ex:
|
||||||
# This hack fixes an impedance mismatch between pkgutil and
|
# This hack fixes an impedance mismatch between pkgutil and
|
||||||
# importlib, where the latter raises other errors for cases where
|
# importlib, where the latter raises other errors for cases where
|
||||||
# pkgutil previously raised ImportError
|
# pkgutil previously raised ImportError
|
||||||
msg = "Error while finding spec for {!r} ({}: {})"
|
msg = "Error while finding spec for {!r} ({}: {})"
|
||||||
raise ImportError(msg.format(mod_name, type(ex), ex)) from ex
|
raise ImportError(msg.format(mod_name, type(ex), ex)) from ex
|
||||||
|
|
||||||
# Helper to get the loader, code and filename for a module
|
|
||||||
def _get_module_details(mod_name):
|
|
||||||
spec = _fixed_find_spec(mod_name)
|
|
||||||
if spec is None:
|
if spec is None:
|
||||||
raise ImportError("No module named %s" % mod_name)
|
raise ImportError("No module named %s" % mod_name)
|
||||||
if spec.submodule_search_locations is not None:
|
if spec.submodule_search_locations is not None:
|
||||||
|
|
|
@ -4,7 +4,6 @@ frozen_init, source_init = util.import_importlib('importlib')
|
||||||
frozen_util, source_util = util.import_importlib('importlib.util')
|
frozen_util, source_util = util.import_importlib('importlib.util')
|
||||||
frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
|
frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
from test import support
|
from test import support
|
||||||
|
@ -13,37 +12,6 @@ import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def temp_module(name, content='', *, pkg=False):
|
|
||||||
conflicts = [n for n in sys.modules if n.partition('.')[0] == name]
|
|
||||||
with support.temp_cwd(None) as cwd:
|
|
||||||
with util.uncache(name, *conflicts):
|
|
||||||
with support.DirsOnSysPath(cwd):
|
|
||||||
frozen_init.invalidate_caches()
|
|
||||||
|
|
||||||
location = os.path.join(cwd, name)
|
|
||||||
if pkg:
|
|
||||||
modpath = os.path.join(location, '__init__.py')
|
|
||||||
os.mkdir(name)
|
|
||||||
else:
|
|
||||||
modpath = location + '.py'
|
|
||||||
if content is None:
|
|
||||||
# Make sure the module file gets created.
|
|
||||||
content = ''
|
|
||||||
if content is not None:
|
|
||||||
# not a namespace package
|
|
||||||
with open(modpath, 'w') as modfile:
|
|
||||||
modfile.write(content)
|
|
||||||
yield location
|
|
||||||
|
|
||||||
|
|
||||||
def submodule(parent, name, pkg_dir, content=''):
|
|
||||||
path = os.path.join(pkg_dir, name + '.py')
|
|
||||||
with open(path, 'w') as subfile:
|
|
||||||
subfile.write(content)
|
|
||||||
return '{}.{}'.format(parent, name), path
|
|
||||||
|
|
||||||
|
|
||||||
class ImportModuleTests:
|
class ImportModuleTests:
|
||||||
|
|
||||||
"""Test importlib.import_module."""
|
"""Test importlib.import_module."""
|
||||||
|
@ -210,121 +178,6 @@ class Source_FindLoaderTests(FindLoaderTests, unittest.TestCase):
|
||||||
init = source_init
|
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'))
|
|
||||||
|
|
||||||
def test_find_submodule(self):
|
|
||||||
name = 'spam'
|
|
||||||
subname = 'ham'
|
|
||||||
with temp_module(name, pkg=True) as pkg_dir:
|
|
||||||
fullname, _ = submodule(name, subname, pkg_dir)
|
|
||||||
spec = self.init.find_spec(fullname, [pkg_dir])
|
|
||||||
self.assertIsNot(spec, None)
|
|
||||||
self.assertNotIn(name, sorted(sys.modules))
|
|
||||||
# Ensure successive calls behave the same.
|
|
||||||
spec_again = self.init.find_spec(fullname, [pkg_dir])
|
|
||||||
self.assertEqual(spec_again, spec)
|
|
||||||
|
|
||||||
def test_find_submodule_missing_path(self):
|
|
||||||
name = 'spam'
|
|
||||||
subname = 'ham'
|
|
||||||
with temp_module(name, pkg=True) as pkg_dir:
|
|
||||||
fullname, _ = submodule(name, subname, pkg_dir)
|
|
||||||
spec = self.init.find_spec(fullname)
|
|
||||||
self.assertIs(spec, None)
|
|
||||||
self.assertNotIn(name, sorted(sys.modules))
|
|
||||||
# Ensure successive calls behave the same.
|
|
||||||
spec = self.init.find_spec(fullname)
|
|
||||||
self.assertIs(spec, None)
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
class ReloadTests:
|
||||||
|
|
||||||
"""Test module reloading for builtin and extension modules."""
|
"""Test module reloading for builtin and extension modules."""
|
||||||
|
@ -484,8 +337,8 @@ class ReloadTests:
|
||||||
# See #19851.
|
# See #19851.
|
||||||
name = 'spam'
|
name = 'spam'
|
||||||
subname = 'ham'
|
subname = 'ham'
|
||||||
with temp_module(name, pkg=True) as pkg_dir:
|
with util.temp_module(name, pkg=True) as pkg_dir:
|
||||||
fullname, _ = submodule(name, subname, pkg_dir)
|
fullname, _ = util.submodule(name, subname, pkg_dir)
|
||||||
ham = self.init.import_module(fullname)
|
ham = self.init.import_module(fullname)
|
||||||
reloaded = self.init.reload(ham)
|
reloaded = self.init.reload(ham)
|
||||||
self.assertIs(reloaded, ham)
|
self.assertIs(reloaded, ham)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from importlib import util
|
from importlib import util
|
||||||
from . import util as test_util
|
from . import util as test_util
|
||||||
|
frozen_init, source_init = test_util.import_importlib('importlib')
|
||||||
|
frozen_machinery, source_machinery = test_util.import_importlib('importlib.machinery')
|
||||||
frozen_util, source_util = test_util.import_importlib('importlib.util')
|
frozen_util, source_util = test_util.import_importlib('importlib.util')
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -310,6 +312,151 @@ Frozen_ResolveNameTests, Source_ResolveNameTests = test_util.test_both(
|
||||||
util=[frozen_util, source_util])
|
util=[frozen_util, source_util])
|
||||||
|
|
||||||
|
|
||||||
|
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 test_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.util.find_spec(name)
|
||||||
|
self.assertEqual(found, spec)
|
||||||
|
|
||||||
|
def test_sys_modules_without___loader__(self):
|
||||||
|
name = 'some_mod'
|
||||||
|
with test_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.util.find_spec(name)
|
||||||
|
self.assertEqual(found, spec)
|
||||||
|
|
||||||
|
def test_sys_modules_spec_is_None(self):
|
||||||
|
name = 'some_mod'
|
||||||
|
with test_util.uncache(name):
|
||||||
|
module = types.ModuleType(name)
|
||||||
|
module.__spec__ = None
|
||||||
|
sys.modules[name] = module
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.util.find_spec(name)
|
||||||
|
|
||||||
|
def test_sys_modules_loader_is_None(self):
|
||||||
|
name = 'some_mod'
|
||||||
|
with test_util.uncache(name):
|
||||||
|
module = types.ModuleType(name)
|
||||||
|
spec = self.machinery.ModuleSpec(name, None)
|
||||||
|
module.__spec__ = spec
|
||||||
|
sys.modules[name] = module
|
||||||
|
found = self.util.find_spec(name)
|
||||||
|
self.assertEqual(found, spec)
|
||||||
|
|
||||||
|
def test_sys_modules_spec_is_not_set(self):
|
||||||
|
name = 'some_mod'
|
||||||
|
with test_util.uncache(name):
|
||||||
|
module = types.ModuleType(name)
|
||||||
|
try:
|
||||||
|
del module.__spec__
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
sys.modules[name] = module
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.util.find_spec(name)
|
||||||
|
|
||||||
|
def test_success(self):
|
||||||
|
name = 'some_mod'
|
||||||
|
with test_util.uncache(name):
|
||||||
|
with test_util.import_state(meta_path=[self.FakeMetaFinder]):
|
||||||
|
self.assertEqual((name, None, None),
|
||||||
|
self.util.find_spec(name))
|
||||||
|
|
||||||
|
# def test_success_path(self):
|
||||||
|
# # Searching on a path should work.
|
||||||
|
# name = 'some_mod'
|
||||||
|
# path = 'path to some place'
|
||||||
|
# with test_util.uncache(name):
|
||||||
|
# with test_util.import_state(meta_path=[self.FakeMetaFinder]):
|
||||||
|
# self.assertEqual((name, path, None),
|
||||||
|
# self.util.find_spec(name, path))
|
||||||
|
|
||||||
|
def test_nothing(self):
|
||||||
|
# None is returned upon failure to find a loader.
|
||||||
|
self.assertIsNone(self.util.find_spec('nevergoingtofindthismodule'))
|
||||||
|
|
||||||
|
def test_find_submodule(self):
|
||||||
|
name = 'spam'
|
||||||
|
subname = 'ham'
|
||||||
|
with test_util.temp_module(name, pkg=True) as pkg_dir:
|
||||||
|
fullname, _ = test_util.submodule(name, subname, pkg_dir)
|
||||||
|
spec = self.util.find_spec(fullname)
|
||||||
|
self.assertIsNot(spec, None)
|
||||||
|
self.assertIn(name, sorted(sys.modules))
|
||||||
|
self.assertNotIn(fullname, sorted(sys.modules))
|
||||||
|
# Ensure successive calls behave the same.
|
||||||
|
spec_again = self.util.find_spec(fullname)
|
||||||
|
self.assertEqual(spec_again, spec)
|
||||||
|
|
||||||
|
def test_find_submodule_parent_already_imported(self):
|
||||||
|
name = 'spam'
|
||||||
|
subname = 'ham'
|
||||||
|
with test_util.temp_module(name, pkg=True) as pkg_dir:
|
||||||
|
self.init.import_module(name)
|
||||||
|
fullname, _ = test_util.submodule(name, subname, pkg_dir)
|
||||||
|
spec = self.util.find_spec(fullname)
|
||||||
|
self.assertIsNot(spec, None)
|
||||||
|
self.assertIn(name, sorted(sys.modules))
|
||||||
|
self.assertNotIn(fullname, sorted(sys.modules))
|
||||||
|
# Ensure successive calls behave the same.
|
||||||
|
spec_again = self.util.find_spec(fullname)
|
||||||
|
self.assertEqual(spec_again, spec)
|
||||||
|
|
||||||
|
def test_find_relative_module(self):
|
||||||
|
name = 'spam'
|
||||||
|
subname = 'ham'
|
||||||
|
with test_util.temp_module(name, pkg=True) as pkg_dir:
|
||||||
|
fullname, _ = test_util.submodule(name, subname, pkg_dir)
|
||||||
|
relname = '.' + subname
|
||||||
|
spec = self.util.find_spec(relname, name)
|
||||||
|
self.assertIsNot(spec, None)
|
||||||
|
self.assertIn(name, sorted(sys.modules))
|
||||||
|
self.assertNotIn(fullname, sorted(sys.modules))
|
||||||
|
# Ensure successive calls behave the same.
|
||||||
|
spec_again = self.util.find_spec(fullname)
|
||||||
|
self.assertEqual(spec_again, spec)
|
||||||
|
|
||||||
|
def test_find_relative_module_missing_package(self):
|
||||||
|
name = 'spam'
|
||||||
|
subname = 'ham'
|
||||||
|
with test_util.temp_module(name, pkg=True) as pkg_dir:
|
||||||
|
fullname, _ = test_util.submodule(name, subname, pkg_dir)
|
||||||
|
relname = '.' + subname
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.util.find_spec(relname)
|
||||||
|
self.assertNotIn(name, sorted(sys.modules))
|
||||||
|
self.assertNotIn(fullname, sorted(sys.modules))
|
||||||
|
|
||||||
|
|
||||||
|
class Frozen_FindSpecTests(FindSpecTests, unittest.TestCase):
|
||||||
|
init = frozen_init
|
||||||
|
machinery = frozen_machinery
|
||||||
|
util = frozen_util
|
||||||
|
|
||||||
|
class Source_FindSpecTests(FindSpecTests, unittest.TestCase):
|
||||||
|
init = source_init
|
||||||
|
machinery = source_machinery
|
||||||
|
util = source_util
|
||||||
|
|
||||||
|
|
||||||
class MagicNumberTests:
|
class MagicNumberTests:
|
||||||
|
|
||||||
def test_length(self):
|
def test_length(self):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from importlib import util
|
from importlib import util, invalidate_caches
|
||||||
import os.path
|
import os.path
|
||||||
from test import support
|
from test import support
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -46,6 +46,13 @@ def case_insensitive_tests(test):
|
||||||
"requires a case-insensitive filesystem")(test)
|
"requires a case-insensitive filesystem")(test)
|
||||||
|
|
||||||
|
|
||||||
|
def submodule(parent, name, pkg_dir, content=''):
|
||||||
|
path = os.path.join(pkg_dir, name + '.py')
|
||||||
|
with open(path, 'w') as subfile:
|
||||||
|
subfile.write(content)
|
||||||
|
return '{}.{}'.format(parent, name), path
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def uncache(*names):
|
def uncache(*names):
|
||||||
"""Uncache a module from sys.modules.
|
"""Uncache a module from sys.modules.
|
||||||
|
@ -71,6 +78,31 @@ def uncache(*names):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def temp_module(name, content='', *, pkg=False):
|
||||||
|
conflicts = [n for n in sys.modules if n.partition('.')[0] == name]
|
||||||
|
with support.temp_cwd(None) as cwd:
|
||||||
|
with uncache(name, *conflicts):
|
||||||
|
with support.DirsOnSysPath(cwd):
|
||||||
|
invalidate_caches()
|
||||||
|
|
||||||
|
location = os.path.join(cwd, name)
|
||||||
|
if pkg:
|
||||||
|
modpath = os.path.join(location, '__init__.py')
|
||||||
|
os.mkdir(name)
|
||||||
|
else:
|
||||||
|
modpath = location + '.py'
|
||||||
|
if content is None:
|
||||||
|
# Make sure the module file gets created.
|
||||||
|
content = ''
|
||||||
|
if content is not None:
|
||||||
|
# not a namespace package
|
||||||
|
with open(modpath, 'w') as modfile:
|
||||||
|
modfile.write(content)
|
||||||
|
yield location
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def import_state(**kwargs):
|
def import_state(**kwargs):
|
||||||
"""Context manager to manage the various importers and stored state in the
|
"""Context manager to manage the various importers and stored state in the
|
||||||
|
|
|
@ -382,6 +382,9 @@ Library
|
||||||
|
|
||||||
- Issue #15475: Add __sizeof__ implementations for itertools objects.
|
- Issue #15475: Add __sizeof__ implementations for itertools objects.
|
||||||
|
|
||||||
|
- Issue #19944: Fix importlib.find_spec() so it imports parents as needed
|
||||||
|
and move the function to importlib.util.
|
||||||
|
|
||||||
- Issue #19880: Fix a reference leak in unittest.TestCase. Explicitly break
|
- Issue #19880: Fix a reference leak in unittest.TestCase. Explicitly break
|
||||||
reference cycles between frames and the _Outcome instance.
|
reference cycles between frames and the _Outcome instance.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue