PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/logilab-astng-0.23.1/manager.py

#
Python | 299 lines | 269 code | 6 blank | 24 comment | 4 complexity | a79aeb41cbbf04cc4c5e4b8e25b918ac MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  2. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. # copyright 2003-2010 Sylvain Thenault, all rights reserved.
  4. # contact mailto:thenault@gmail.com
  5. #
  6. # This file is part of logilab-astng.
  7. #
  8. # logilab-astng is free software: you can redistribute it and/or modify it
  9. # under the terms of the GNU Lesser General Public License as published by the
  10. # Free Software Foundation, either version 2.1 of the License, or (at your
  11. # option) any later version.
  12. #
  13. # logilab-astng is distributed in the hope that it will be useful, but
  14. # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  16. # for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
  20. """astng manager: avoid multiple astng build of a same module when
  21. possible by providing a class responsible to get astng representation
  22. from various source and using a cache of built modules)
  23. """
  24. __docformat__ = "restructuredtext en"
  25. import sys
  26. import os
  27. from os.path import dirname, basename, abspath, join, isdir, exists
  28. from logilab.common.modutils import NoSourceFile, is_python_source, \
  29. file_from_modpath, load_module_from_name, modpath_from_file, \
  30. get_module_files, get_source_file, zipimport
  31. from logilab.common.configuration import OptionsProviderMixIn
  32. from logilab.astng.exceptions import ASTNGBuildingException
  33. def astng_wrapper(func, modname):
  34. """wrapper to give to ASTNGManager.project_from_files"""
  35. print 'parsing %s...' % modname
  36. try:
  37. return func(modname)
  38. except ASTNGBuildingException, exc:
  39. print exc
  40. except Exception, exc:
  41. import traceback
  42. traceback.print_exc()
  43. def _silent_no_wrap(func, modname):
  44. """silent wrapper that doesn't do anything; can be used for tests"""
  45. return func(modname)
  46. def safe_repr(obj):
  47. try:
  48. return repr(obj)
  49. except:
  50. return '???'
  51. class ASTNGManager(OptionsProviderMixIn):
  52. """the astng manager, responsible to build astng from files
  53. or modules.
  54. Use the Borg pattern.
  55. """
  56. name = 'astng loader'
  57. options = (("ignore",
  58. {'type' : "csv", 'metavar' : "<file>",
  59. 'dest' : "black_list", "default" : ('CVS',),
  60. 'help' : "add <file> (may be a directory) to the black list\
  61. . It should be a base name, not a path. You may set this option multiple times\
  62. ."}),
  63. ("project",
  64. {'default': "No Name", 'type' : 'string', 'short': 'p',
  65. 'metavar' : '<project name>',
  66. 'help' : 'set the project name.'}),
  67. )
  68. brain = {}
  69. def __init__(self):
  70. self.__dict__ = ASTNGManager.brain
  71. if not self.__dict__:
  72. OptionsProviderMixIn.__init__(self)
  73. self.load_defaults()
  74. # NOTE: cache entries are added by the [re]builder
  75. self.astng_cache = {}
  76. self._mod_file_cache = {}
  77. self.transformers = []
  78. def astng_from_file(self, filepath, modname=None, fallback=True, source=False):
  79. """given a module name, return the astng object"""
  80. try:
  81. filepath = get_source_file(filepath, include_no_ext=True)
  82. source = True
  83. except NoSourceFile:
  84. pass
  85. if modname is None:
  86. try:
  87. modname = '.'.join(modpath_from_file(filepath))
  88. except ImportError:
  89. modname = filepath
  90. if modname in self.astng_cache:
  91. return self.astng_cache[modname]
  92. if source:
  93. from logilab.astng.builder import ASTNGBuilder
  94. return ASTNGBuilder(self).file_build(filepath, modname)
  95. elif fallback and modname:
  96. return self.astng_from_module_name(modname)
  97. raise ASTNGBuildingException('unable to get astng for file %s' %
  98. filepath)
  99. def astng_from_module_name(self, modname, context_file=None):
  100. """given a module name, return the astng object"""
  101. if modname in self.astng_cache:
  102. return self.astng_cache[modname]
  103. if modname == '__main__':
  104. from logilab.astng.builder import ASTNGBuilder
  105. return ASTNGBuilder(self).string_build('', modname)
  106. old_cwd = os.getcwd()
  107. if context_file:
  108. os.chdir(dirname(context_file))
  109. try:
  110. filepath = self.file_from_module_name(modname, context_file)
  111. if filepath is not None and not is_python_source(filepath):
  112. module = self.zip_import_data(filepath)
  113. if module is not None:
  114. return module
  115. if filepath is None or not is_python_source(filepath):
  116. try:
  117. module = load_module_from_name(modname)
  118. except Exception, ex:
  119. msg = 'Unable to load module %s (%s)' % (modname, ex)
  120. raise ASTNGBuildingException(msg)
  121. return self.astng_from_module(module, modname)
  122. return self.astng_from_file(filepath, modname, fallback=False)
  123. finally:
  124. os.chdir(old_cwd)
  125. def zip_import_data(self, filepath):
  126. if zipimport is None:
  127. return None
  128. from logilab.astng.builder import ASTNGBuilder
  129. builder = ASTNGBuilder(self)
  130. for ext in ('.zip', '.egg'):
  131. try:
  132. eggpath, resource = filepath.rsplit(ext + '/', 1)
  133. except ValueError:
  134. continue
  135. try:
  136. importer = zipimport.zipimporter(eggpath + ext)
  137. zmodname = resource.replace('/', '.')
  138. if importer.is_package(resource):
  139. zmodname = zmodname + '.__init__'
  140. module = builder.string_build(importer.get_source(resource),
  141. zmodname, filepath)
  142. return module
  143. except:
  144. continue
  145. return None
  146. def file_from_module_name(self, modname, contextfile):
  147. try:
  148. value = self._mod_file_cache[(modname, contextfile)]
  149. except KeyError:
  150. try:
  151. value = file_from_modpath(modname.split('.'),
  152. context_file=contextfile)
  153. except ImportError, ex:
  154. msg = 'Unable to load module %s (%s)' % (modname, ex)
  155. value = ASTNGBuildingException(msg)
  156. self._mod_file_cache[(modname, contextfile)] = value
  157. if isinstance(value, ASTNGBuildingException):
  158. raise value
  159. return value
  160. def astng_from_module(self, module, modname=None):
  161. """given an imported module, return the astng object"""
  162. modname = modname or module.__name__
  163. if modname in self.astng_cache:
  164. return self.astng_cache[modname]
  165. try:
  166. # some builtin modules don't have __file__ attribute
  167. filepath = module.__file__
  168. if is_python_source(filepath):
  169. return self.astng_from_file(filepath, modname)
  170. except AttributeError:
  171. pass
  172. from logilab.astng.builder import ASTNGBuilder
  173. return ASTNGBuilder(self).module_build(module, modname)
  174. def astng_from_class(self, klass, modname=None):
  175. """get astng for the given class"""
  176. if modname is None:
  177. try:
  178. modname = klass.__module__
  179. except AttributeError:
  180. raise ASTNGBuildingException(
  181. 'Unable to get module for class %s' % safe_repr(klass))
  182. modastng = self.astng_from_module_name(modname)
  183. return modastng.getattr(klass.__name__)[0] # XXX
  184. def infer_astng_from_something(self, obj, context=None):
  185. """infer astng for the given class"""
  186. if hasattr(obj, '__class__') and not isinstance(obj, type):
  187. klass = obj.__class__
  188. else:
  189. klass = obj
  190. try:
  191. modname = klass.__module__
  192. except AttributeError:
  193. raise ASTNGBuildingException(
  194. 'Unable to get module for %s' % safe_repr(klass))
  195. except Exception, ex:
  196. raise ASTNGBuildingException(
  197. 'Unexpected error while retrieving module for %s: %s'
  198. % (safe_repr(klass), ex))
  199. try:
  200. name = klass.__name__
  201. except AttributeError:
  202. raise ASTNGBuildingException(
  203. 'Unable to get name for %s' % safe_repr(klass))
  204. except Exception, ex:
  205. raise ASTNGBuildingException(
  206. 'Unexpected error while retrieving name for %s: %s'
  207. % (safe_repr(klass), ex))
  208. # take care, on living object __module__ is regularly wrong :(
  209. modastng = self.astng_from_module_name(modname)
  210. if klass is obj:
  211. for infered in modastng.igetattr(name, context):
  212. yield infered
  213. else:
  214. for infered in modastng.igetattr(name, context):
  215. yield infered.instanciate_class()
  216. def project_from_files(self, files, func_wrapper=astng_wrapper,
  217. project_name=None, black_list=None):
  218. """return a Project from a list of files or modules"""
  219. # build the project representation
  220. project_name = project_name or self.config.project
  221. black_list = black_list or self.config.black_list
  222. project = Project(project_name)
  223. for something in files:
  224. if not exists(something):
  225. fpath = file_from_modpath(something.split('.'))
  226. elif isdir(something):
  227. fpath = join(something, '__init__.py')
  228. else:
  229. fpath = something
  230. astng = func_wrapper(self.astng_from_file, fpath)
  231. if astng is None:
  232. continue
  233. # XXX why is first file defining the project.path ?
  234. project.path = project.path or astng.file
  235. project.add_module(astng)
  236. base_name = astng.name
  237. # recurse in package except if __init__ was explicitly given
  238. if astng.package and something.find('__init__') == -1:
  239. # recurse on others packages / modules if this is a package
  240. for fpath in get_module_files(dirname(astng.file),
  241. black_list):
  242. astng = func_wrapper(self.astng_from_file, fpath)
  243. if astng is None or astng.name == base_name:
  244. continue
  245. project.add_module(astng)
  246. return project
  247. def register_transformer(self, transformer):
  248. self.transformers.append(transformer)
  249. class Project:
  250. """a project handle a set of modules / packages"""
  251. def __init__(self, name=''):
  252. self.name = name
  253. self.path = None
  254. self.modules = []
  255. self.locals = {}
  256. self.__getitem__ = self.locals.__getitem__
  257. self.__iter__ = self.locals.__iter__
  258. self.values = self.locals.values
  259. self.keys = self.locals.keys
  260. self.items = self.locals.items
  261. def add_module(self, node):
  262. self.locals[node.name] = node
  263. self.modules.append(node)
  264. def get_module(self, name):
  265. return self.locals[name]
  266. def get_children(self):
  267. return self.modules
  268. def __repr__(self):
  269. return '<Project %r at %s (%s modules)>' % (self.name, id(self),
  270. len(self.modules))