1 """Utility code for constructing importers, etc."""
2 from ._abc import Loader
3 from ._bootstrap import module_from_spec
4 from ._bootstrap import _resolve_name
5 from ._bootstrap import spec_from_loader
6 from ._bootstrap import _find_spec
7 from ._bootstrap_external import MAGIC_NUMBER
8 from ._bootstrap_external import _RAW_MAGIC_NUMBER
9 from ._bootstrap_external import cache_from_source
10 from ._bootstrap_external import decode_source
11 from ._bootstrap_external import source_from_cache
12 from ._bootstrap_external import spec_from_file_location
13
14 import _imp
15 import sys
16 import types
17
18
19 def source_hash(source_bytes):
20 "Return the hash of *source_bytes* as used in hash-based pyc files."
21 return _imp.source_hash(_RAW_MAGIC_NUMBER, source_bytes)
22
23
24 def resolve_name(name, package):
25 """Resolve a relative module name to an absolute one."""
26 if not name.startswith('.'):
27 return name
28 elif not package:
29 raise ImportError(f'no package specified for {repr(name)} '
30 '(required for relative module names)')
31 level = 0
32 for character in name:
33 if character != '.':
34 break
35 level += 1
36 return _resolve_name(name[level:], package, level)
37
38
39 def _find_spec_from_path(name, path=None):
40 """Return the spec for the specified module.
41
42 First, sys.modules is checked to see if the module was already imported. If
43 so, then sys.modules[name].__spec__ is returned. If that happens to be
44 set to None, then ValueError is raised. If the module is not in
45 sys.modules, then sys.meta_path is searched for a suitable spec with the
46 value of 'path' given to the finders. None is returned if no spec could
47 be found.
48
49 Dotted names do not have their parent packages implicitly imported. You will
50 most likely need to explicitly import all parent packages in the proper
51 order for a submodule to get the correct spec.
52
53 """
54 if name not in sys.modules:
55 return _find_spec(name, path)
56 else:
57 module = sys.modules[name]
58 if module is None:
59 return None
60 try:
61 spec = module.__spec__
62 except AttributeError:
63 raise ValueError(f'{name}.__spec__ is not set') from None
64 else:
65 if spec is None:
66 raise ValueError(f'{name}.__spec__ is None')
67 return spec
68
69
70 def find_spec(name, package=None):
71 """Return the spec for the specified module.
72
73 First, sys.modules is checked to see if the module was already imported. If
74 so, then sys.modules[name].__spec__ is returned. If that happens to be
75 set to None, then ValueError is raised. If the module is not in
76 sys.modules, then sys.meta_path is searched for a suitable spec with the
77 value of 'path' given to the finders. None is returned if no spec could
78 be found.
79
80 If the name is for submodule (contains a dot), the parent module is
81 automatically imported.
82
83 The name and package arguments work the same as importlib.import_module().
84 In other words, relative module names (with leading dots) work.
85
86 """
87 fullname = resolve_name(name, package) if name.startswith('.') else name
88 if fullname not in sys.modules:
89 parent_name = fullname.rpartition('.')[0]
90 if parent_name:
91 parent = __import__(parent_name, fromlist=['__path__'])
92 try:
93 parent_path = parent.__path__
94 except AttributeError as e:
95 raise ModuleNotFoundError(
96 f"__path__ attribute not found on {parent_name!r} "
97 f"while trying to find {fullname!r}", name=fullname) from e
98 else:
99 parent_path = None
100 return _find_spec(fullname, parent_path)
101 else:
102 module = sys.modules[fullname]
103 if module is None:
104 return None
105 try:
106 spec = module.__spec__
107 except AttributeError:
108 raise ValueError(f'{name}.__spec__ is not set') from None
109 else:
110 if spec is None:
111 raise ValueError(f'{name}.__spec__ is None')
112 return spec
113
114
115 # Normally we would use contextlib.contextmanager. However, this module
116 # is imported by runpy, which means we want to avoid any unnecessary
117 # dependencies. Thus we use a class.
118
119 class ESC[4;38;5;81m_incompatible_extension_module_restrictions:
120 """A context manager that can temporarily skip the compatibility check.
121
122 NOTE: This function is meant to accommodate an unusual case; one
123 which is likely to eventually go away. There's is a pretty good
124 chance this is not what you were looking for.
125
126 WARNING: Using this function to disable the check can lead to
127 unexpected behavior and even crashes. It should only be used during
128 extension module development.
129
130 If "disable_check" is True then the compatibility check will not
131 happen while the context manager is active. Otherwise the check
132 *will* happen.
133
134 Normally, extensions that do not support multiple interpreters
135 may not be imported in a subinterpreter. That implies modules
136 that do not implement multi-phase init or that explicitly of out.
137
138 Likewise for modules import in a subinterpeter with its own GIL
139 when the extension does not support a per-interpreter GIL. This
140 implies the module does not have a Py_mod_multiple_interpreters slot
141 set to Py_MOD_PER_INTERPRETER_GIL_SUPPORTED.
142
143 In both cases, this context manager may be used to temporarily
144 disable the check for compatible extension modules.
145
146 You can get the same effect as this function by implementing the
147 basic interface of multi-phase init (PEP 489) and lying about
148 support for mulitple interpreters (or per-interpreter GIL).
149 """
150
151 def __init__(self, *, disable_check):
152 self.disable_check = bool(disable_check)
153
154 def __enter__(self):
155 self.old = _imp._override_multi_interp_extensions_check(self.override)
156 return self
157
158 def __exit__(self, *args):
159 old = self.old
160 del self.old
161 _imp._override_multi_interp_extensions_check(old)
162
163 @property
164 def override(self):
165 return -1 if self.disable_check else 1
166
167
168 class ESC[4;38;5;81m_LazyModule(ESC[4;38;5;149mtypesESC[4;38;5;149m.ESC[4;38;5;149mModuleType):
169
170 """A subclass of the module type which triggers loading upon attribute access."""
171
172 def __getattribute__(self, attr):
173 """Trigger the load of the module and return the attribute."""
174 # All module metadata must be garnered from __spec__ in order to avoid
175 # using mutated values.
176 # Stop triggering this method.
177 self.__class__ = types.ModuleType
178 # Get the original name to make sure no object substitution occurred
179 # in sys.modules.
180 original_name = self.__spec__.name
181 # Figure out exactly what attributes were mutated between the creation
182 # of the module and now.
183 attrs_then = self.__spec__.loader_state['__dict__']
184 attrs_now = self.__dict__
185 attrs_updated = {}
186 for key, value in attrs_now.items():
187 # Code that set the attribute may have kept a reference to the
188 # assigned object, making identity more important than equality.
189 if key not in attrs_then:
190 attrs_updated[key] = value
191 elif id(attrs_now[key]) != id(attrs_then[key]):
192 attrs_updated[key] = value
193 self.__spec__.loader.exec_module(self)
194 # If exec_module() was used directly there is no guarantee the module
195 # object was put into sys.modules.
196 if original_name in sys.modules:
197 if id(self) != id(sys.modules[original_name]):
198 raise ValueError(f"module object for {original_name!r} "
199 "substituted in sys.modules during a lazy "
200 "load")
201 # Update after loading since that's what would happen in an eager
202 # loading situation.
203 self.__dict__.update(attrs_updated)
204 return getattr(self, attr)
205
206 def __delattr__(self, attr):
207 """Trigger the load and then perform the deletion."""
208 # To trigger the load and raise an exception if the attribute
209 # doesn't exist.
210 self.__getattribute__(attr)
211 delattr(self, attr)
212
213
214 class ESC[4;38;5;81mLazyLoader(ESC[4;38;5;149mLoader):
215
216 """A loader that creates a module which defers loading until attribute access."""
217
218 @staticmethod
219 def __check_eager_loader(loader):
220 if not hasattr(loader, 'exec_module'):
221 raise TypeError('loader must define exec_module()')
222
223 @classmethod
224 def factory(cls, loader):
225 """Construct a callable which returns the eager loader made lazy."""
226 cls.__check_eager_loader(loader)
227 return lambda *args, **kwargs: cls(loader(*args, **kwargs))
228
229 def __init__(self, loader):
230 self.__check_eager_loader(loader)
231 self.loader = loader
232
233 def create_module(self, spec):
234 return self.loader.create_module(spec)
235
236 def exec_module(self, module):
237 """Make the module load lazily."""
238 module.__spec__.loader = self.loader
239 module.__loader__ = self.loader
240 # Don't need to worry about deep-copying as trying to set an attribute
241 # on an object would have triggered the load,
242 # e.g. ``module.__spec__.loader = None`` would trigger a load from
243 # trying to access module.__spec__.
244 loader_state = {}
245 loader_state['__dict__'] = module.__dict__.copy()
246 loader_state['__class__'] = module.__class__
247 module.__spec__.loader_state = loader_state
248 module.__class__ = _LazyModule