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.

manager.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. # Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  2. # Copyright (c) 2014-2016 Claudiu Popa <pcmanticore@gmail.com>
  3. # Copyright (c) 2014 Google, Inc.
  4. # Copyright (c) 2015-2016 Cara Vinson <ceridwenv@gmail.com>
  5. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  6. # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
  7. """astroid manager: avoid multiple astroid build of a same module when
  8. possible by providing a class responsible to get astroid representation
  9. from various source and using a cache of built modules)
  10. """
  11. import os
  12. import sys
  13. import zipimport
  14. import six
  15. from astroid import exceptions
  16. from astroid.interpreter._import import spec
  17. from astroid import modutils
  18. from astroid import transforms
  19. from astroid import util
  20. def safe_repr(obj):
  21. try:
  22. return repr(obj)
  23. except Exception: # pylint: disable=broad-except
  24. return '???'
  25. class AstroidManager(object):
  26. """the astroid manager, responsible to build astroid from files
  27. or modules.
  28. Use the Borg pattern.
  29. """
  30. name = 'astroid loader'
  31. brain = {}
  32. def __init__(self):
  33. self.__dict__ = AstroidManager.brain
  34. if not self.__dict__:
  35. # NOTE: cache entries are added by the [re]builder
  36. self.astroid_cache = {}
  37. self._mod_file_cache = {}
  38. self._failed_import_hooks = []
  39. self.always_load_extensions = False
  40. self.optimize_ast = False
  41. self.extension_package_whitelist = set()
  42. self._transform = transforms.TransformVisitor()
  43. # Export these APIs for convenience
  44. self.register_transform = self._transform.register_transform
  45. self.unregister_transform = self._transform.unregister_transform
  46. def visit_transforms(self, node):
  47. """Visit the transforms and apply them to the given *node*."""
  48. return self._transform.visit(node)
  49. def ast_from_file(self, filepath, modname=None, fallback=True, source=False):
  50. """given a module name, return the astroid object"""
  51. try:
  52. filepath = modutils.get_source_file(filepath, include_no_ext=True)
  53. source = True
  54. except modutils.NoSourceFile:
  55. pass
  56. if modname is None:
  57. try:
  58. modname = '.'.join(modutils.modpath_from_file(filepath))
  59. except ImportError:
  60. modname = filepath
  61. if modname in self.astroid_cache and self.astroid_cache[modname].file == filepath:
  62. return self.astroid_cache[modname]
  63. if source:
  64. from astroid.builder import AstroidBuilder
  65. return AstroidBuilder(self).file_build(filepath, modname)
  66. elif fallback and modname:
  67. return self.ast_from_module_name(modname)
  68. raise exceptions.AstroidBuildingError(
  69. 'Unable to build an AST for {path}.', path=filepath)
  70. def _build_stub_module(self, modname):
  71. from astroid.builder import AstroidBuilder
  72. return AstroidBuilder(self).string_build('', modname)
  73. def _build_namespace_module(self, modname, path):
  74. from astroid.builder import build_namespace_package_module
  75. return build_namespace_package_module(modname, path)
  76. def _can_load_extension(self, modname):
  77. if self.always_load_extensions:
  78. return True
  79. if modutils.is_standard_module(modname):
  80. return True
  81. parts = modname.split('.')
  82. return any(
  83. '.'.join(parts[:x]) in self.extension_package_whitelist
  84. for x in range(1, len(parts) + 1))
  85. def ast_from_module_name(self, modname, context_file=None):
  86. """given a module name, return the astroid object"""
  87. if modname in self.astroid_cache:
  88. return self.astroid_cache[modname]
  89. if modname == '__main__':
  90. return self._build_stub_module(modname)
  91. old_cwd = os.getcwd()
  92. if context_file:
  93. os.chdir(os.path.dirname(context_file))
  94. try:
  95. found_spec = self.file_from_module_name(modname, context_file)
  96. # pylint: disable=no-member
  97. if found_spec.type == spec.ModuleType.PY_ZIPMODULE:
  98. # pylint: disable=no-member
  99. module = self.zip_import_data(found_spec.location)
  100. if module is not None:
  101. return module
  102. elif found_spec.type in (spec.ModuleType.C_BUILTIN,
  103. spec.ModuleType.C_EXTENSION):
  104. # pylint: disable=no-member
  105. if (found_spec.type == spec.ModuleType.C_EXTENSION
  106. and not self._can_load_extension(modname)):
  107. return self._build_stub_module(modname)
  108. try:
  109. module = modutils.load_module_from_name(modname)
  110. except Exception as ex: # pylint: disable=broad-except
  111. util.reraise(exceptions.AstroidImportError(
  112. 'Loading {modname} failed with:\n{error}',
  113. modname=modname, path=found_spec.location, error=ex))
  114. return self.ast_from_module(module, modname)
  115. elif found_spec.type == spec.ModuleType.PY_COMPILED:
  116. raise exceptions.AstroidImportError(
  117. "Unable to load compiled module {modname}.",
  118. # pylint: disable=no-member
  119. modname=modname, path=found_spec.location)
  120. elif found_spec.type == spec.ModuleType.PY_NAMESPACE:
  121. return self._build_namespace_module(modname,
  122. # pylint: disable=no-member
  123. found_spec.submodule_search_locations)
  124. # pylint: disable=no-member
  125. if found_spec.location is None:
  126. raise exceptions.AstroidImportError(
  127. "Can't find a file for module {modname}.",
  128. modname=modname)
  129. # pylint: disable=no-member
  130. return self.ast_from_file(found_spec.location, modname, fallback=False)
  131. except exceptions.AstroidBuildingError as e:
  132. for hook in self._failed_import_hooks:
  133. try:
  134. return hook(modname)
  135. except exceptions.AstroidBuildingError:
  136. pass
  137. raise e
  138. finally:
  139. os.chdir(old_cwd)
  140. def zip_import_data(self, filepath):
  141. if zipimport is None:
  142. return None
  143. from astroid.builder import AstroidBuilder
  144. builder = AstroidBuilder(self)
  145. for ext in ('.zip', '.egg'):
  146. try:
  147. eggpath, resource = filepath.rsplit(ext + os.path.sep, 1)
  148. except ValueError:
  149. continue
  150. try:
  151. importer = zipimport.zipimporter(eggpath + ext)
  152. zmodname = resource.replace(os.path.sep, '.')
  153. if importer.is_package(resource):
  154. zmodname = zmodname + '.__init__'
  155. module = builder.string_build(importer.get_source(resource),
  156. zmodname, filepath)
  157. return module
  158. except Exception: # pylint: disable=broad-except
  159. continue
  160. return None
  161. def file_from_module_name(self, modname, contextfile):
  162. try:
  163. value = self._mod_file_cache[(modname, contextfile)]
  164. traceback = sys.exc_info()[2]
  165. except KeyError:
  166. try:
  167. value = modutils.file_info_from_modpath(
  168. modname.split('.'), context_file=contextfile)
  169. traceback = sys.exc_info()[2]
  170. except ImportError as ex:
  171. value = exceptions.AstroidImportError(
  172. 'Failed to import module {modname} with error:\n{error}.',
  173. modname=modname, error=ex)
  174. traceback = sys.exc_info()[2]
  175. self._mod_file_cache[(modname, contextfile)] = value
  176. if isinstance(value, exceptions.AstroidBuildingError):
  177. six.reraise(exceptions.AstroidBuildingError,
  178. value, traceback)
  179. return value
  180. def ast_from_module(self, module, modname=None):
  181. """given an imported module, return the astroid object"""
  182. modname = modname or module.__name__
  183. if modname in self.astroid_cache:
  184. return self.astroid_cache[modname]
  185. try:
  186. # some builtin modules don't have __file__ attribute
  187. filepath = module.__file__
  188. if modutils.is_python_source(filepath):
  189. return self.ast_from_file(filepath, modname)
  190. except AttributeError:
  191. pass
  192. from astroid.builder import AstroidBuilder
  193. return AstroidBuilder(self).module_build(module, modname)
  194. def ast_from_class(self, klass, modname=None):
  195. """get astroid for the given class"""
  196. if modname is None:
  197. try:
  198. modname = klass.__module__
  199. except AttributeError:
  200. util.reraise(exceptions.AstroidBuildingError(
  201. 'Unable to get module for class {class_name}.',
  202. cls=klass, class_repr=safe_repr(klass), modname=modname))
  203. modastroid = self.ast_from_module_name(modname)
  204. return modastroid.getattr(klass.__name__)[0] # XXX
  205. def infer_ast_from_something(self, obj, context=None):
  206. """infer astroid for the given class"""
  207. if hasattr(obj, '__class__') and not isinstance(obj, type):
  208. klass = obj.__class__
  209. else:
  210. klass = obj
  211. try:
  212. modname = klass.__module__
  213. except AttributeError:
  214. util.reraise(exceptions.AstroidBuildingError(
  215. 'Unable to get module for {class_repr}.',
  216. cls=klass, class_repr=safe_repr(klass)))
  217. except Exception as ex: # pylint: disable=broad-except
  218. util.reraise(exceptions.AstroidImportError(
  219. 'Unexpected error while retrieving module for {class_repr}:\n'
  220. '{error}', cls=klass, class_repr=safe_repr(klass), error=ex))
  221. try:
  222. name = klass.__name__
  223. except AttributeError:
  224. util.reraise(exceptions.AstroidBuildingError(
  225. 'Unable to get name for {class_repr}:\n',
  226. cls=klass, class_repr=safe_repr(klass)))
  227. except Exception as ex: # pylint: disable=broad-except
  228. util.reraise(exceptions.AstroidImportError(
  229. 'Unexpected error while retrieving name for {class_repr}:\n'
  230. '{error}', cls=klass, class_repr=safe_repr(klass), error=ex))
  231. # take care, on living object __module__ is regularly wrong :(
  232. modastroid = self.ast_from_module_name(modname)
  233. if klass is obj:
  234. for inferred in modastroid.igetattr(name, context):
  235. yield inferred
  236. else:
  237. for inferred in modastroid.igetattr(name, context):
  238. yield inferred.instantiate_class()
  239. def register_failed_import_hook(self, hook):
  240. """Registers a hook to resolve imports that cannot be found otherwise.
  241. `hook` must be a function that accepts a single argument `modname` which
  242. contains the name of the module or package that could not be imported.
  243. If `hook` can resolve the import, must return a node of type `astroid.Module`,
  244. otherwise, it must raise `AstroidBuildingError`.
  245. """
  246. self._failed_import_hooks.append(hook)
  247. def cache_module(self, module):
  248. """Cache a module if no module with the same name is known yet."""
  249. self.astroid_cache.setdefault(module.name, module)
  250. def clear_cache(self, astroid_builtin=None):
  251. # XXX clear transforms
  252. self.astroid_cache.clear()
  253. # force bootstrap again, else we may ends up with cache inconsistency
  254. # between the manager and CONST_PROXY, making
  255. # unittest_lookup.LookupTC.test_builtin_lookup fail depending on the
  256. # test order
  257. import astroid.raw_building
  258. astroid.raw_building._astroid_bootstrapping(
  259. astroid_builtin=astroid_builtin)