Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

importer.py 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """This module implements a post import hook mechanism styled after what is
  2. described in PEP-369. Note that it doesn't cope with modules being reloaded.
  3. """
  4. import sys
  5. import threading
  6. PY2 = sys.version_info[0] == 2
  7. PY3 = sys.version_info[0] == 3
  8. if PY3:
  9. import importlib
  10. string_types = str,
  11. else:
  12. string_types = basestring,
  13. from .decorators import synchronized
  14. # The dictionary registering any post import hooks to be triggered once
  15. # the target module has been imported. Once a module has been imported
  16. # and the hooks fired, the list of hooks recorded against the target
  17. # module will be truncacted but the list left in the dictionary. This
  18. # acts as a flag to indicate that the module had already been imported.
  19. _post_import_hooks = {}
  20. _post_import_hooks_init = False
  21. _post_import_hooks_lock = threading.RLock()
  22. # Register a new post import hook for the target module name. This
  23. # differs from the PEP-369 implementation in that it also allows the
  24. # hook function to be specified as a string consisting of the name of
  25. # the callback in the form 'module:function'. This will result in a
  26. # proxy callback being registered which will defer loading of the
  27. # specified module containing the callback function until required.
  28. def _create_import_hook_from_string(name):
  29. def import_hook(module):
  30. module_name, function = name.split(':')
  31. attrs = function.split('.')
  32. __import__(module_name)
  33. callback = sys.modules[module_name]
  34. for attr in attrs:
  35. callback = getattr(callback, attr)
  36. return callback(module)
  37. return import_hook
  38. @synchronized(_post_import_hooks_lock)
  39. def register_post_import_hook(hook, name):
  40. # Create a deferred import hook if hook is a string name rather than
  41. # a callable function.
  42. if isinstance(hook, string_types):
  43. hook = _create_import_hook_from_string(hook)
  44. # Automatically install the import hook finder if it has not already
  45. # been installed.
  46. global _post_import_hooks_init
  47. if not _post_import_hooks_init:
  48. _post_import_hooks_init = True
  49. sys.meta_path.insert(0, ImportHookFinder())
  50. # Determine if any prior registration of a post import hook for
  51. # the target modules has occurred and act appropriately.
  52. hooks = _post_import_hooks.get(name, None)
  53. if hooks is None:
  54. # No prior registration of post import hooks for the target
  55. # module. We need to check whether the module has already been
  56. # imported. If it has we fire the hook immediately and add an
  57. # empty list to the registry to indicate that the module has
  58. # already been imported and hooks have fired. Otherwise add
  59. # the post import hook to the registry.
  60. module = sys.modules.get(name, None)
  61. if module is not None:
  62. _post_import_hooks[name] = []
  63. hook(module)
  64. else:
  65. _post_import_hooks[name] = [hook]
  66. elif hooks == []:
  67. # A prior registration of port import hooks for the target
  68. # module was done and the hooks already fired. Fire the hook
  69. # immediately.
  70. module = sys.modules[name]
  71. hook(module)
  72. else:
  73. # A prior registration of port import hooks for the target
  74. # module was done but the module has not yet been imported.
  75. _post_import_hooks[name].append(hook)
  76. # Register post import hooks defined as package entry points.
  77. def _create_import_hook_from_entrypoint(entrypoint):
  78. def import_hook(module):
  79. __import__(entrypoint.module_name)
  80. callback = sys.modules[entrypoint.module_name]
  81. for attr in entrypoint.attrs:
  82. callback = getattr(callback, attr)
  83. return callback(module)
  84. return import_hook
  85. def discover_post_import_hooks(group):
  86. try:
  87. import pkg_resources
  88. except ImportError:
  89. return
  90. for entrypoint in pkg_resources.iter_entry_points(group=group):
  91. callback = _create_import_hook_from_entrypoint(entrypoint)
  92. register_post_import_hook(callback, entrypoint.name)
  93. # Indicate that a module has been loaded. Any post import hooks which
  94. # were registered against the target module will be invoked. If an
  95. # exception is raised in any of the post import hooks, that will cause
  96. # the import of the target module to fail.
  97. @synchronized(_post_import_hooks_lock)
  98. def notify_module_loaded(module):
  99. name = getattr(module, '__name__', None)
  100. hooks = _post_import_hooks.get(name, None)
  101. if hooks:
  102. _post_import_hooks[name] = []
  103. for hook in hooks:
  104. hook(module)
  105. # A custom module import finder. This intercepts attempts to import
  106. # modules and watches out for attempts to import target modules of
  107. # interest. When a module of interest is imported, then any post import
  108. # hooks which are registered will be invoked.
  109. class _ImportHookLoader:
  110. def load_module(self, fullname):
  111. module = sys.modules[fullname]
  112. notify_module_loaded(module)
  113. return module
  114. class _ImportHookChainedLoader:
  115. def __init__(self, loader):
  116. self.loader = loader
  117. def load_module(self, fullname):
  118. module = self.loader.load_module(fullname)
  119. notify_module_loaded(module)
  120. return module
  121. class ImportHookFinder:
  122. def __init__(self):
  123. self.in_progress = {}
  124. @synchronized(_post_import_hooks_lock)
  125. def find_module(self, fullname, path=None):
  126. # If the module being imported is not one we have registered
  127. # post import hooks for, we can return immediately. We will
  128. # take no further part in the importing of this module.
  129. if not fullname in _post_import_hooks:
  130. return None
  131. # When we are interested in a specific module, we will call back
  132. # into the import system a second time to defer to the import
  133. # finder that is supposed to handle the importing of the module.
  134. # We set an in progress flag for the target module so that on
  135. # the second time through we don't trigger another call back
  136. # into the import system and cause a infinite loop.
  137. if fullname in self.in_progress:
  138. return None
  139. self.in_progress[fullname] = True
  140. # Now call back into the import system again.
  141. try:
  142. if PY3:
  143. # For Python 3 we need to use find_loader() from
  144. # the importlib module. It doesn't actually
  145. # import the target module and only finds the
  146. # loader. If a loader is found, we need to return
  147. # our own loader which will then in turn call the
  148. # real loader to import the module and invoke the
  149. # post import hooks.
  150. loader = importlib.find_loader(fullname, path)
  151. if loader:
  152. return _ImportHookChainedLoader(loader)
  153. else:
  154. # For Python 2 we don't have much choice but to
  155. # call back in to __import__(). This will
  156. # actually cause the module to be imported. If no
  157. # module could be found then ImportError will be
  158. # raised. Otherwise we return a loader which
  159. # returns the already loaded module and invokes
  160. # the post import hooks.
  161. __import__(fullname)
  162. return _ImportHookLoader()
  163. finally:
  164. del self.in_progress[fullname]
  165. # Decorator for marking that a function should be called as a post
  166. # import hook when the target module is imported.
  167. def when_imported(name):
  168. def register(hook):
  169. register_post_import_hook(hook, name)
  170. return hook
  171. return register