util.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. """Utility code for constructing importers, etc."""
  2. from . import abc
  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. from contextlib import contextmanager
  14. import _imp
  15. import functools
  16. import sys
  17. import types
  18. import warnings
  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. def resolve_name(name, package):
  23. """Resolve a relative module name to an absolute one."""
  24. if not name.startswith('.'):
  25. return name
  26. elif not package:
  27. raise ImportError(f'no package specified for {repr(name)} '
  28. '(required for relative module names)')
  29. level = 0
  30. for character in name:
  31. if character != '.':
  32. break
  33. level += 1
  34. return _resolve_name(name[level:], package, level)
  35. def _find_spec_from_path(name, path=None):
  36. """Return the spec for the specified module.
  37. First, sys.modules is checked to see if the module was already imported. If
  38. so, then sys.modules[name].__spec__ is returned. If that happens to be
  39. set to None, then ValueError is raised. If the module is not in
  40. sys.modules, then sys.meta_path is searched for a suitable spec with the
  41. value of 'path' given to the finders. None is returned if no spec could
  42. be found.
  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. if name not in sys.modules:
  48. return _find_spec(name, path)
  49. else:
  50. module = sys.modules[name]
  51. if module is None:
  52. return None
  53. try:
  54. spec = module.__spec__
  55. except AttributeError:
  56. raise ValueError('{}.__spec__ is not set'.format(name)) from None
  57. else:
  58. if spec is None:
  59. raise ValueError('{}.__spec__ is None'.format(name))
  60. return spec
  61. def find_spec(name, package=None):
  62. """Return the spec for the specified module.
  63. First, sys.modules is checked to see if the module was already imported. If
  64. so, then sys.modules[name].__spec__ is returned. If that happens to be
  65. set to None, then ValueError is raised. If the module is not in
  66. sys.modules, then sys.meta_path is searched for a suitable spec with the
  67. value of 'path' given to the finders. None is returned if no spec could
  68. be found.
  69. If the name is for submodule (contains a dot), the parent module is
  70. automatically imported.
  71. The name and package arguments work the same as importlib.import_module().
  72. In other words, relative module names (with leading dots) work.
  73. """
  74. fullname = resolve_name(name, package) if name.startswith('.') else name
  75. if fullname not in sys.modules:
  76. parent_name = fullname.rpartition('.')[0]
  77. if parent_name:
  78. parent = __import__(parent_name, fromlist=['__path__'])
  79. try:
  80. parent_path = parent.__path__
  81. except AttributeError as e:
  82. raise ModuleNotFoundError(
  83. f"__path__ attribute not found on {parent_name!r} "
  84. f"while trying to find {fullname!r}", name=fullname) from e
  85. else:
  86. parent_path = None
  87. return _find_spec(fullname, parent_path)
  88. else:
  89. module = sys.modules[fullname]
  90. if module is None:
  91. return None
  92. try:
  93. spec = module.__spec__
  94. except AttributeError:
  95. raise ValueError('{}.__spec__ is not set'.format(name)) from None
  96. else:
  97. if spec is None:
  98. raise ValueError('{}.__spec__ is None'.format(name))
  99. return spec
  100. @contextmanager
  101. def _module_to_load(name):
  102. is_reload = name in sys.modules
  103. module = sys.modules.get(name)
  104. if not is_reload:
  105. # This must be done before open() is called as the 'io' module
  106. # implicitly imports 'locale' and would otherwise trigger an
  107. # infinite loop.
  108. module = type(sys)(name)
  109. # This must be done before putting the module in sys.modules
  110. # (otherwise an optimization shortcut in import.c becomes wrong)
  111. module.__initializing__ = True
  112. sys.modules[name] = module
  113. try:
  114. yield module
  115. except Exception:
  116. if not is_reload:
  117. try:
  118. del sys.modules[name]
  119. except KeyError:
  120. pass
  121. finally:
  122. module.__initializing__ = False
  123. def set_package(fxn):
  124. """Set __package__ on the returned module.
  125. This function is deprecated.
  126. """
  127. @functools.wraps(fxn)
  128. def set_package_wrapper(*args, **kwargs):
  129. warnings.warn('The import system now takes care of this automatically.',
  130. DeprecationWarning, stacklevel=2)
  131. module = fxn(*args, **kwargs)
  132. if getattr(module, '__package__', None) is None:
  133. module.__package__ = module.__name__
  134. if not hasattr(module, '__path__'):
  135. module.__package__ = module.__package__.rpartition('.')[0]
  136. return module
  137. return set_package_wrapper
  138. def set_loader(fxn):
  139. """Set __loader__ on the returned module.
  140. This function is deprecated.
  141. """
  142. @functools.wraps(fxn)
  143. def set_loader_wrapper(self, *args, **kwargs):
  144. warnings.warn('The import system now takes care of this automatically.',
  145. DeprecationWarning, stacklevel=2)
  146. module = fxn(self, *args, **kwargs)
  147. if getattr(module, '__loader__', None) is None:
  148. module.__loader__ = self
  149. return module
  150. return set_loader_wrapper
  151. def module_for_loader(fxn):
  152. """Decorator to handle selecting the proper module for loaders.
  153. The decorated function is passed the module to use instead of the module
  154. name. The module passed in to the function is either from sys.modules if
  155. it already exists or is a new module. If the module is new, then __name__
  156. is set the first argument to the method, __loader__ is set to self, and
  157. __package__ is set accordingly (if self.is_package() is defined) will be set
  158. before it is passed to the decorated function (if self.is_package() does
  159. not work for the module it will be set post-load).
  160. If an exception is raised and the decorator created the module it is
  161. subsequently removed from sys.modules.
  162. The decorator assumes that the decorated function takes the module name as
  163. the second argument.
  164. """
  165. warnings.warn('The import system now takes care of this automatically.',
  166. DeprecationWarning, stacklevel=2)
  167. @functools.wraps(fxn)
  168. def module_for_loader_wrapper(self, fullname, *args, **kwargs):
  169. with _module_to_load(fullname) as module:
  170. module.__loader__ = self
  171. try:
  172. is_package = self.is_package(fullname)
  173. except (ImportError, AttributeError):
  174. pass
  175. else:
  176. if is_package:
  177. module.__package__ = fullname
  178. else:
  179. module.__package__ = fullname.rpartition('.')[0]
  180. # If __package__ was not set above, __import__() will do it later.
  181. return fxn(self, module, *args, **kwargs)
  182. return module_for_loader_wrapper
  183. class _LazyModule(types.ModuleType):
  184. """A subclass of the module type which triggers loading upon attribute access."""
  185. def __getattribute__(self, attr):
  186. """Trigger the load of the module and return the attribute."""
  187. # All module metadata must be garnered from __spec__ in order to avoid
  188. # using mutated values.
  189. # Stop triggering this method.
  190. self.__class__ = types.ModuleType
  191. # Get the original name to make sure no object substitution occurred
  192. # in sys.modules.
  193. original_name = self.__spec__.name
  194. # Figure out exactly what attributes were mutated between the creation
  195. # of the module and now.
  196. attrs_then = self.__spec__.loader_state['__dict__']
  197. original_type = self.__spec__.loader_state['__class__']
  198. attrs_now = self.__dict__
  199. attrs_updated = {}
  200. for key, value in attrs_now.items():
  201. # Code that set the attribute may have kept a reference to the
  202. # assigned object, making identity more important than equality.
  203. if key not in attrs_then:
  204. attrs_updated[key] = value
  205. elif id(attrs_now[key]) != id(attrs_then[key]):
  206. attrs_updated[key] = value
  207. self.__spec__.loader.exec_module(self)
  208. # If exec_module() was used directly there is no guarantee the module
  209. # object was put into sys.modules.
  210. if original_name in sys.modules:
  211. if id(self) != id(sys.modules[original_name]):
  212. raise ValueError(f"module object for {original_name!r} "
  213. "substituted in sys.modules during a lazy "
  214. "load")
  215. # Update after loading since that's what would happen in an eager
  216. # loading situation.
  217. self.__dict__.update(attrs_updated)
  218. return getattr(self, attr)
  219. def __delattr__(self, attr):
  220. """Trigger the load and then perform the deletion."""
  221. # To trigger the load and raise an exception if the attribute
  222. # doesn't exist.
  223. self.__getattribute__(attr)
  224. delattr(self, attr)
  225. class LazyLoader(abc.Loader):
  226. """A loader that creates a module which defers loading until attribute access."""
  227. @staticmethod
  228. def __check_eager_loader(loader):
  229. if not hasattr(loader, 'exec_module'):
  230. raise TypeError('loader must define exec_module()')
  231. @classmethod
  232. def factory(cls, loader):
  233. """Construct a callable which returns the eager loader made lazy."""
  234. cls.__check_eager_loader(loader)
  235. return lambda *args, **kwargs: cls(loader(*args, **kwargs))
  236. def __init__(self, loader):
  237. self.__check_eager_loader(loader)
  238. self.loader = loader
  239. def create_module(self, spec):
  240. return self.loader.create_module(spec)
  241. def exec_module(self, module):
  242. """Make the module load lazily."""
  243. module.__spec__.loader = self.loader
  244. module.__loader__ = self.loader
  245. # Don't need to worry about deep-copying as trying to set an attribute
  246. # on an object would have triggered the load,
  247. # e.g. ``module.__spec__.loader = None`` would trigger a load from
  248. # trying to access module.__spec__.
  249. loader_state = {}
  250. loader_state['__dict__'] = module.__dict__.copy()
  251. loader_state['__class__'] = module.__class__
  252. module.__spec__.loader_state = loader_state
  253. module.__class__ = _LazyModule