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.

spec.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. # Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
  2. import abc
  3. import collections
  4. import enum
  5. import imp
  6. import os
  7. import sys
  8. import zipimport
  9. try:
  10. import importlib.machinery
  11. _HAS_MACHINERY = True
  12. except ImportError:
  13. _HAS_MACHINERY = False
  14. try:
  15. from functools import lru_cache
  16. except ImportError:
  17. from backports.functools_lru_cache import lru_cache
  18. from . import util
  19. ModuleType = enum.Enum('ModuleType', 'C_BUILTIN C_EXTENSION PKG_DIRECTORY '
  20. 'PY_CODERESOURCE PY_COMPILED PY_FROZEN PY_RESOURCE '
  21. 'PY_SOURCE PY_ZIPMODULE PY_NAMESPACE')
  22. _ImpTypes = {imp.C_BUILTIN: ModuleType.C_BUILTIN,
  23. imp.C_EXTENSION: ModuleType.C_EXTENSION,
  24. imp.PKG_DIRECTORY: ModuleType.PKG_DIRECTORY,
  25. imp.PY_COMPILED: ModuleType.PY_COMPILED,
  26. imp.PY_FROZEN: ModuleType.PY_FROZEN,
  27. imp.PY_SOURCE: ModuleType.PY_SOURCE,
  28. }
  29. if hasattr(imp, 'PY_RESOURCE'):
  30. _ImpTypes[imp.PY_RESOURCE] = ModuleType.PY_RESOURCE
  31. if hasattr(imp, 'PY_CODERESOURCE'):
  32. _ImpTypes[imp.PY_CODERESOURCE] = ModuleType.PY_CODERESOURCE
  33. def _imp_type_to_module_type(imp_type):
  34. return _ImpTypes[imp_type]
  35. _ModuleSpec = collections.namedtuple('_ModuleSpec', 'name type location '
  36. 'origin submodule_search_locations')
  37. class ModuleSpec(_ModuleSpec):
  38. """Defines a class similar to PEP 420's ModuleSpec
  39. A module spec defines a name of a module, its type, location
  40. and where submodules can be found, if the module is a package.
  41. """
  42. def __new__(cls, name, module_type, location=None, origin=None,
  43. submodule_search_locations=None):
  44. return _ModuleSpec.__new__(cls, name=name, type=module_type,
  45. location=location, origin=origin,
  46. submodule_search_locations=submodule_search_locations)
  47. class Finder(object):
  48. """A finder is a class which knows how to find a particular module."""
  49. def __init__(self, path=None):
  50. self._path = path or sys.path
  51. @abc.abstractmethod
  52. def find_module(self, modname, module_parts, processed, submodule_path):
  53. """Find the given module
  54. Each finder is responsible for each protocol of finding, as long as
  55. they all return a ModuleSpec.
  56. :param str modname: The module which needs to be searched.
  57. :param list module_parts: It should be a list of strings,
  58. where each part contributes to the module's
  59. namespace.
  60. :param list processed: What parts from the module parts were processed
  61. so far.
  62. :param list submodule_path: A list of paths where the module
  63. can be looked into.
  64. :returns: A ModuleSpec, describing how and where the module was found,
  65. None, otherwise.
  66. """
  67. def contribute_to_path(self, spec, processed):
  68. """Get a list of extra paths where this finder can search."""
  69. class ImpFinder(Finder):
  70. """A finder based on the imp module."""
  71. def find_module(self, modname, module_parts, processed, submodule_path):
  72. if submodule_path is not None:
  73. submodule_path = list(submodule_path)
  74. try:
  75. stream, mp_filename, mp_desc = imp.find_module(modname, submodule_path)
  76. except ImportError:
  77. return None
  78. # Close resources.
  79. if stream:
  80. stream.close()
  81. return ModuleSpec(name=modname, location=mp_filename,
  82. module_type=_imp_type_to_module_type(mp_desc[2]))
  83. def contribute_to_path(self, spec, processed):
  84. if spec.location is None:
  85. # Builtin.
  86. return None
  87. if _is_setuptools_namespace(spec.location):
  88. # extend_path is called, search sys.path for module/packages
  89. # of this name see pkgutil.extend_path documentation
  90. path = [os.path.join(p, *processed) for p in sys.path
  91. if os.path.isdir(os.path.join(p, *processed))]
  92. else:
  93. path = [spec.location]
  94. return path
  95. class ExplicitNamespacePackageFinder(ImpFinder):
  96. """A finder for the explicit namespace packages, generated through pkg_resources."""
  97. def find_module(self, modname, module_parts, processed, submodule_path):
  98. if processed:
  99. modname = '.'.join(processed + [modname])
  100. if util.is_namespace(modname) and modname in sys.modules:
  101. submodule_path = sys.modules[modname].__path__
  102. return ModuleSpec(name=modname, location='',
  103. origin='namespace',
  104. module_type=ModuleType.PY_NAMESPACE,
  105. submodule_search_locations=submodule_path)
  106. return None
  107. def contribute_to_path(self, spec, processed):
  108. return spec.submodule_search_locations
  109. class ZipFinder(Finder):
  110. """Finder that knows how to find a module inside zip files."""
  111. def __init__(self, path):
  112. super(ZipFinder, self).__init__(path)
  113. self._zipimporters = _precache_zipimporters(path)
  114. def find_module(self, modname, module_parts, processed, submodule_path):
  115. try:
  116. file_type, filename, path = _search_zip(module_parts, self._zipimporters)
  117. except ImportError:
  118. return None
  119. return ModuleSpec(name=modname, location=filename,
  120. origin='egg', module_type=file_type,
  121. submodule_search_locations=path)
  122. class PathSpecFinder(Finder):
  123. """Finder based on importlib.machinery.PathFinder."""
  124. def find_module(self, modname, module_parts, processed, submodule_path):
  125. spec = importlib.machinery.PathFinder.find_spec(modname, path=submodule_path)
  126. if spec:
  127. location = spec.origin if spec.origin != 'namespace' else None
  128. module_type = ModuleType.PY_NAMESPACE if spec.origin == 'namespace' else None
  129. spec = ModuleSpec(name=spec.name, location=location,
  130. origin=spec.origin, module_type=module_type,
  131. submodule_search_locations=list(spec.submodule_search_locations
  132. or []))
  133. return spec
  134. def contribute_to_path(self, spec, processed):
  135. if spec.type == ModuleType.PY_NAMESPACE:
  136. return spec.submodule_search_locations
  137. return None
  138. _SPEC_FINDERS = (
  139. ImpFinder,
  140. ZipFinder,
  141. )
  142. if _HAS_MACHINERY and sys.version_info[:2] >= (3, 4):
  143. _SPEC_FINDERS += (PathSpecFinder, )
  144. _SPEC_FINDERS += (ExplicitNamespacePackageFinder, )
  145. def _is_setuptools_namespace(location):
  146. try:
  147. with open(os.path.join(location, '__init__.py'), 'rb') as stream:
  148. data = stream.read(4096)
  149. except IOError:
  150. pass
  151. else:
  152. extend_path = b'pkgutil' in data and b'extend_path' in data
  153. declare_namespace = (
  154. b"pkg_resources" in data
  155. and b"declare_namespace(__name__)" in data)
  156. return extend_path or declare_namespace
  157. @lru_cache()
  158. def _cached_set_diff(left, right):
  159. result = set(left)
  160. result.difference_update(right)
  161. return result
  162. def _precache_zipimporters(path=None):
  163. pic = sys.path_importer_cache
  164. # When measured, despite having the same complexity (O(n)),
  165. # converting to tuples and then caching the conversion to sets
  166. # and the set difference is faster than converting to sets
  167. # and then only caching the set difference.
  168. req_paths = tuple(path or sys.path)
  169. cached_paths = tuple(pic)
  170. new_paths = _cached_set_diff(req_paths, cached_paths)
  171. for entry_path in new_paths:
  172. try:
  173. pic[entry_path] = zipimport.zipimporter(entry_path)
  174. except zipimport.ZipImportError:
  175. continue
  176. return pic
  177. def _search_zip(modpath, pic):
  178. for filepath, importer in list(pic.items()):
  179. if importer is not None:
  180. found = importer.find_module(modpath[0])
  181. if found:
  182. if not importer.find_module(os.path.sep.join(modpath)):
  183. raise ImportError('No module named %s in %s/%s' % (
  184. '.'.join(modpath[1:]), filepath, modpath))
  185. #import code; code.interact(local=locals())
  186. return (ModuleType.PY_ZIPMODULE,
  187. os.path.abspath(filepath) + os.path.sep + os.path.sep.join(modpath),
  188. filepath)
  189. raise ImportError('No module named %s' % '.'.join(modpath))
  190. def _find_spec_with_path(search_path, modname, module_parts, processed, submodule_path):
  191. finders = [finder(search_path) for finder in _SPEC_FINDERS]
  192. for finder in finders:
  193. spec = finder.find_module(modname, module_parts, processed, submodule_path)
  194. if spec is None:
  195. continue
  196. return finder, spec
  197. raise ImportError('No module named %s' % '.'.join(module_parts))
  198. def find_spec(modpath, path=None):
  199. """Find a spec for the given module.
  200. :type modpath: list or tuple
  201. :param modpath:
  202. split module's name (i.e name of a module or package split
  203. on '.'), with leading empty strings for explicit relative import
  204. :type path: list or None
  205. :param path:
  206. optional list of path where the module or package should be
  207. searched (use sys.path if nothing or None is given)
  208. :rtype: ModuleSpec
  209. :return: A module spec, which describes how the module was
  210. found and where.
  211. """
  212. _path = path or sys.path
  213. # Need a copy for not mutating the argument.
  214. modpath = modpath[:]
  215. submodule_path = None
  216. module_parts = modpath[:]
  217. processed = []
  218. while modpath:
  219. modname = modpath.pop(0)
  220. finder, spec = _find_spec_with_path(_path, modname,
  221. module_parts, processed,
  222. submodule_path or path)
  223. processed.append(modname)
  224. if modpath:
  225. submodule_path = finder.contribute_to_path(spec, processed)
  226. if spec.type == ModuleType.PKG_DIRECTORY:
  227. spec = spec._replace(submodule_search_locations=submodule_path)
  228. return spec