Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6029e08

Browse files
Issue 19944: Fix importlib.find_spec() so it imports parents as needed.
The function is also moved to importlib.util.
1 parent 128ee22 commit 6029e08

11 files changed

Lines changed: 292 additions & 230 deletions

File tree

Doc/library/importlib.rst

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,6 @@ Functions
8989
.. versionchanged:: 3.3
9090
Parent packages are automatically imported.
9191

92-
.. function:: find_spec(name, path=None)
93-
94-
Find the :term:`spec <module spec>` for a module, optionally within the
95-
specified *path*. If the module is in :attr:`sys.modules`, then
96-
``sys.modules[name].__spec__`` is returned (unless the spec would be
97-
``None`` or is not set, in which case :exc:`ValueError` is raised).
98-
Otherwise a search using :attr:`sys.meta_path` is done. ``None`` is
99-
returned if no spec is found.
100-
101-
A dotted name does not have its parent implicitly imported as that requires
102-
loading them and that may not be desired. To properly import a submodule you
103-
will need to import all parent packages of the submodule and use the correct
104-
argument to *path*.
105-
106-
.. versionadded:: 3.4
107-
10892
.. function:: find_loader(name, path=None)
10993

11094
Find the loader for a module, optionally within the specified *path*. If the
@@ -125,7 +109,7 @@ Functions
125109
attribute is set to ``None``.
126110

127111
.. deprecated:: 3.4
128-
Use :func:`find_spec` instead.
112+
Use :func:`importlib.util.find_spec` instead.
129113

130114
.. function:: invalidate_caches()
131115

@@ -1111,6 +1095,22 @@ an :term:`importer`.
11111095

11121096
.. versionadded:: 3.3
11131097

1098+
.. function:: find_spec(name, package=None)
1099+
1100+
Find the :term:`spec <module spec>` for a module, optionally relative to
1101+
the specified **package** name. If the module is in :attr:`sys.modules`,
1102+
then ``sys.modules[name].__spec__`` is returned (unless the spec would be
1103+
``None`` or is not set, in which case :exc:`ValueError` is raised).
1104+
Otherwise a search using :attr:`sys.meta_path` is done. ``None`` is
1105+
returned if no spec is found.
1106+
1107+
If **name** is for a submodule (contains a dot), the parent module is
1108+
automatically imported.
1109+
1110+
**name** and **package** work the same as for :func:`import_module`.
1111+
1112+
.. versionadded:: 3.4
1113+
11141114
.. decorator:: module_for_loader
11151115

11161116
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`

Lib/idlelib/EditorWindow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import importlib
22
import importlib.abc
3+
import importlib.util
34
import os
45
from platform import python_version
56
import re
@@ -660,7 +661,7 @@ def open_module(self, event=None):
660661
return
661662
# XXX Ought to insert current file's directory in front of path
662663
try:
663-
spec = importlib.find_spec(name)
664+
spec = importlib.util.find_spec(name)
664665
except (ValueError, ImportError) as msg:
665666
tkMessageBox.showerror("Import error", str(msg), parent=self.text)
666667
return

Lib/importlib/__init__.py

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
# initialised below if the frozen one is not available).
1212
import _imp # Just the builtin component, NOT the full Python module
1313
import sys
14-
import types
15-
import warnings
1614

1715
try:
1816
import _frozen_importlib as _bootstrap
@@ -34,6 +32,10 @@
3432
# Fully bootstrapped at this point, import whatever you like, circular
3533
# dependencies and startup overhead minimisation permitting :)
3634

35+
import types
36+
import warnings
37+
38+
3739
# Public API #########################################################
3840

3941
from ._bootstrap import __import__
@@ -47,47 +49,16 @@ def invalidate_caches():
4749
finder.invalidate_caches()
4850

4951

50-
def find_spec(name, path=None):
51-
"""Return the spec for the specified module.
52-
53-
First, sys.modules is checked to see if the module was already imported. If
54-
so, then sys.modules[name].__spec__ is returned. If that happens to be
55-
set to None, then ValueError is raised. If the module is not in
56-
sys.modules, then sys.meta_path is searched for a suitable spec with the
57-
value of 'path' given to the finders. None is returned if no spec could
58-
be found.
59-
60-
Dotted names do not have their parent packages implicitly imported. You will
61-
most likely need to explicitly import all parent packages in the proper
62-
order for a submodule to get the correct spec.
63-
64-
"""
65-
if name not in sys.modules:
66-
return _bootstrap._find_spec(name, path)
67-
else:
68-
module = sys.modules[name]
69-
if module is None:
70-
return None
71-
try:
72-
spec = module.__spec__
73-
except AttributeError:
74-
raise ValueError('{}.__spec__ is not set'.format(name))
75-
else:
76-
if spec is None:
77-
raise ValueError('{}.__spec__ is None'.format(name))
78-
return spec
79-
80-
8152
def find_loader(name, path=None):
8253
"""Return the loader for the specified module.
8354
8455
This is a backward-compatible wrapper around find_spec().
8556
86-
This function is deprecated in favor of importlib.find_spec().
57+
This function is deprecated in favor of importlib.util.find_spec().
8758
8859
"""
89-
warnings.warn('Use importlib.find_spec() instead.', DeprecationWarning,
90-
stacklevel=2)
60+
warnings.warn('Use importlib.util.find_spec() instead.',
61+
DeprecationWarning, stacklevel=2)
9162
try:
9263
loader = sys.modules[name].__loader__
9364
if loader is None:
@@ -167,7 +138,8 @@ def reload(module):
167138
pkgpath = parent.__path__
168139
else:
169140
pkgpath = None
170-
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, module)
141+
target = module
142+
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, target)
171143
methods = _bootstrap._SpecMethods(spec)
172144
methods.exec(module)
173145
# The module may have replaced itself in sys.modules!

Lib/importlib/util.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ._bootstrap import spec_from_loader
88
from ._bootstrap import spec_from_file_location
99
from ._bootstrap import _resolve_name
10+
from ._bootstrap import _find_spec
1011

1112
from contextlib import contextmanager
1213
import functools
@@ -29,6 +30,77 @@ def resolve_name(name, package):
2930
return _resolve_name(name[level:], package, level)
3031

3132

33+
def _find_spec_from_path(name, path=None):
34+
"""Return the spec for the specified module.
35+
36+
First, sys.modules is checked to see if the module was already imported. If
37+
so, then sys.modules[name].__spec__ is returned. If that happens to be
38+
set to None, then ValueError is raised. If the module is not in
39+
sys.modules, then sys.meta_path is searched for a suitable spec with the
40+
value of 'path' given to the finders. None is returned if no spec could
41+
be found.
42+
43+
Dotted names do not have their parent packages implicitly imported. You will
44+
most likely need to explicitly import all parent packages in the proper
45+
order for a submodule to get the correct spec.
46+
47+
"""
48+
if name not in sys.modules:
49+
return _find_spec(name, path)
50+
else:
51+
module = sys.modules[name]
52+
if module is None:
53+
return None
54+
try:
55+
spec = module.__spec__
56+
except AttributeError:
57+
raise ValueError('{}.__spec__ is not set'.format(name))
58+
else:
59+
if spec is None:
60+
raise ValueError('{}.__spec__ is None'.format(name))
61+
return spec
62+
63+
64+
def find_spec(name, package=None):
65+
"""Return the spec for the specified module.
66+
67+
First, sys.modules is checked to see if the module was already imported. If
68+
so, then sys.modules[name].__spec__ is returned. If that happens to be
69+
set to None, then ValueError is raised. If the module is not in
70+
sys.modules, then sys.meta_path is searched for a suitable spec with the
71+
value of 'path' given to the finders. None is returned if no spec could
72+
be found.
73+
74+
If the name is for submodule (contains a dot), the parent module is
75+
automatically imported.
76+
77+
The name and package arguments work the same as importlib.import_module().
78+
In other words, relative module names (with leading dots) work.
79+
80+
"""
81+
fullname = resolve_name(name, package) if name.startswith('.') else name
82+
if fullname not in sys.modules:
83+
parent_name = fullname.rpartition('.')[0]
84+
if parent_name:
85+
# Use builtins.__import__() in case someone replaced it.
86+
parent = __import__(parent_name, fromlist=['__path__'])
87+
return _find_spec(fullname, parent.__path__)
88+
else:
89+
return _find_spec(fullname, None)
90+
else:
91+
module = sys.modules[fullname]
92+
if module is None:
93+
return None
94+
try:
95+
spec = module.__spec__
96+
except AttributeError:
97+
raise ValueError('{}.__spec__ is not set'.format(name))
98+
else:
99+
if spec is None:
100+
raise ValueError('{}.__spec__ is None'.format(name))
101+
return spec
102+
103+
32104
@contextmanager
33105
def _module_to_load(name):
34106
is_reload = name in sys.modules

Lib/pkgutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ def get_data(package, resource):
611611
which does not support get_data(), then None is returned.
612612
"""
613613

614-
spec = importlib.find_spec(package)
614+
spec = importlib.util.find_spec(package)
615615
if spec is None:
616616
return None
617617
loader = spec.loader

Lib/pyclbr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import io
4343
import os
4444
import sys
45-
import importlib
45+
import importlib.util
4646
import tokenize
4747
from token import NAME, DEDENT, OP
4848
from operator import itemgetter
@@ -141,7 +141,7 @@ def _readmodule(module, path, inpackage=None):
141141
else:
142142
search_path = path + sys.path
143143
# XXX This will change once issue19944 lands.
144-
spec = importlib.find_spec(fullmodule, search_path)
144+
spec = importlib.util._find_spec_from_path(fullmodule, search_path)
145145
fname = spec.loader.get_filename(fullmodule)
146146
_modules[fullmodule] = dict
147147
if spec.loader.is_package(fullmodule):

Lib/runpy.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
import os
1414
import sys
1515
import importlib.machinery # importlib first so we can test #15386 via -m
16+
import importlib.util
1617
import types
17-
from importlib import find_spec
18-
from importlib.util import spec_from_loader
1918
from pkgutil import read_code, get_importer
2019

2120
__all__ = [
@@ -100,33 +99,16 @@ def _run_module_code(code, init_globals=None,
10099
# may be cleared when the temporary module goes away
101100
return mod_globals.copy()
102101

103-
104-
def _fixed_find_spec(mod_name):
105-
# find_spec has the same annoying behaviour as find_loader did (it
106-
# fails to work properly for dotted names), so this is a fixed version
107-
# ala pkgutil.get_loader
108-
if mod_name.startswith('.'):
109-
msg = "Relative module name {!r} not supported".format(mod_name)
110-
raise ImportError(msg)
111-
path = None
112-
pkg_name = mod_name.rpartition(".")[0]
113-
if pkg_name:
114-
pkg = importlib.import_module(pkg_name)
115-
path = getattr(pkg, "__path__", None)
116-
if path is None:
117-
return None
102+
# Helper to get the loader, code and filename for a module
103+
def _get_module_details(mod_name):
118104
try:
119-
return importlib.find_spec(mod_name, path)
105+
spec = importlib.util.find_spec(mod_name)
120106
except (ImportError, AttributeError, TypeError, ValueError) as ex:
121107
# This hack fixes an impedance mismatch between pkgutil and
122108
# importlib, where the latter raises other errors for cases where
123109
# pkgutil previously raised ImportError
124110
msg = "Error while finding spec for {!r} ({}: {})"
125111
raise ImportError(msg.format(mod_name, type(ex), ex)) from ex
126-
127-
# Helper to get the loader, code and filename for a module
128-
def _get_module_details(mod_name):
129-
spec = _fixed_find_spec(mod_name)
130112
if spec is None:
131113
raise ImportError("No module named %s" % mod_name)
132114
if spec.submodule_search_locations is not None:

0 commit comments

Comments
 (0)