diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -46,7 +46,43 @@ finder.invalidate_caches() -def find_spec(name, path=None): +def _resolve_level(name, package): + level = 0 + if name.startswith('.'): + if not package: + msg = ("the 'package' argument is required to perform a relative " + "import for {!r}") + raise TypeError(msg.format(name)) + for character in name: + if character != '.': + break + level += 1 + return name[level:], level + + +# This is an adaptation of importlib._bootstrap._find_and_load_unlocked(). +def _find_unlocked(name): + path = None + parent = name.rpartition('.')[0] + if parent: + if parent not in sys.modules: + _bootstrap._call_with_frames_removed(_bootstrap._gcd_import, + parent) + # Crazy side-effects! + if name in sys.modules: + return None, sys.modules[name] + parent_module = sys.modules[parent] + try: + path = parent_module.__path__ + except AttributeError: + msg = (_bootstrap._ERR_MSG + '; {!r} is not a package' + ).format(name, parent) + raise ImportError(msg, name=name) + return _bootstrap._find_spec(name, path) + + +# This borrows from importlib._bootstrap._gcd_import(). +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 @@ -56,25 +92,36 @@ 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 the module is a submodule, its parent will be imported as a side + effect of finding the spec. However, the module itself will not be + explicitly imported. + + The 'package' argument is required when performing a relative import. It + specifies the package to use as the anchor point from which to resolve the + relative import to an absolute import. """ + name, level = _resolve_level(name, package) + _bootstrap._sanity_check(name, package, level) + if level > 0: + name = _bootstrap._resolve_name(name, package, level) + _imp.acquire_lock() if name not in sys.modules: - return _bootstrap._find_spec(name, path) + spec, module = _find_unlocked(name) + if module is None: + return spec 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 + 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... @@ -116,17 +163,8 @@ relative import to an absolute import. """ - level = 0 - if name.startswith('.'): - if not package: - msg = ("the 'package' argument is required to perform a relative " - "import for {!r}") - raise TypeError(msg.format(name)) - for character in name: - if character != '.': - break - level += 1 - return _bootstrap._gcd_import(name[level:], package, level) + name, level = _resolve_level(name, package) + return _bootstrap._gcd_import(name, package, level) _RELOADING = {}