Index: third_party/logilab/astroid/manager.py |
=================================================================== |
--- third_party/logilab/astroid/manager.py (revision 0) |
+++ third_party/logilab/astroid/manager.py (working copy) |
@@ -0,0 +1,347 @@ |
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
+# |
+# This file is part of astroid. |
+# |
+# astroid is free software: you can redistribute it and/or modify it |
+# under the terms of the GNU Lesser General Public License as published by the |
+# Free Software Foundation, either version 2.1 of the License, or (at your |
+# option) any later version. |
+# |
+# astroid is distributed in the hope that it will be useful, but |
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
+# for more details. |
+# |
+# You should have received a copy of the GNU Lesser General Public License along |
+# with astroid. If not, see <http://www.gnu.org/licenses/>. |
+"""astroid manager: avoid multiple astroid build of a same module when |
+possible by providing a class responsible to get astroid representation |
+from various source and using a cache of built modules) |
+""" |
+ |
+__docformat__ = "restructuredtext en" |
+ |
+import os |
+from os.path import dirname, join, isdir, exists |
+from warnings import warn |
+ |
+from logilab.common.configuration import OptionsProviderMixIn |
+ |
+from astroid.exceptions import AstroidBuildingException |
+from astroid.modutils import NoSourceFile, is_python_source, \ |
+ file_from_modpath, load_module_from_name, modpath_from_file, \ |
+ get_module_files, get_source_file, zipimport |
+ |
+ |
+def astroid_wrapper(func, modname): |
+ """wrapper to give to AstroidManager.project_from_files""" |
+ print 'parsing %s...' % modname |
+ try: |
+ return func(modname) |
+ except AstroidBuildingException, exc: |
+ print exc |
+ except Exception, exc: |
+ import traceback |
+ traceback.print_exc() |
+ |
+def _silent_no_wrap(func, modname): |
+ """silent wrapper that doesn't do anything; can be used for tests""" |
+ return func(modname) |
+ |
+def safe_repr(obj): |
+ try: |
+ return repr(obj) |
+ except: |
+ return '???' |
+ |
+ |
+ |
+class AstroidManager(OptionsProviderMixIn): |
+ """the astroid manager, responsible to build astroid from files |
+ or modules. |
+ |
+ Use the Borg pattern. |
+ """ |
+ |
+ name = 'astroid loader' |
+ options = (("ignore", |
+ {'type' : "csv", 'metavar' : "<file>", |
+ 'dest' : "black_list", "default" : ('CVS',), |
+ 'help' : "add <file> (may be a directory) to the black list\ |
+. It should be a base name, not a path. You may set this option multiple times\ |
+."}), |
+ ("project", |
+ {'default': "No Name", 'type' : 'string', 'short': 'p', |
+ 'metavar' : '<project name>', |
+ 'help' : 'set the project name.'}), |
+ ) |
+ brain = {} |
+ def __init__(self): |
+ self.__dict__ = AstroidManager.brain |
+ if not self.__dict__: |
+ OptionsProviderMixIn.__init__(self) |
+ self.load_defaults() |
+ # NOTE: cache entries are added by the [re]builder |
+ self.astroid_cache = {} |
+ self._mod_file_cache = {} |
+ self.transforms = {} |
+ |
+ def ast_from_file(self, filepath, modname=None, fallback=True, source=False): |
+ """given a module name, return the astroid object""" |
+ try: |
+ filepath = get_source_file(filepath, include_no_ext=True) |
+ source = True |
+ except NoSourceFile: |
+ pass |
+ if modname is None: |
+ try: |
+ modname = '.'.join(modpath_from_file(filepath)) |
+ except ImportError: |
+ modname = filepath |
+ if modname in self.astroid_cache and self.astroid_cache[modname].file == filepath: |
+ return self.astroid_cache[modname] |
+ if source: |
+ from astroid.builder import AstroidBuilder |
+ return AstroidBuilder(self).file_build(filepath, modname) |
+ elif fallback and modname: |
+ return self.ast_from_module_name(modname) |
+ raise AstroidBuildingException('unable to get astroid for file %s' % |
+ filepath) |
+ |
+ def ast_from_module_name(self, modname, context_file=None): |
+ """given a module name, return the astroid object""" |
+ if modname in self.astroid_cache: |
+ return self.astroid_cache[modname] |
+ if modname == '__main__': |
+ from astroid.builder import AstroidBuilder |
+ return AstroidBuilder(self).string_build('', modname) |
+ old_cwd = os.getcwd() |
+ if context_file: |
+ os.chdir(dirname(context_file)) |
+ try: |
+ filepath = self.file_from_module_name(modname, context_file) |
+ if filepath is not None and not is_python_source(filepath): |
+ module = self.zip_import_data(filepath) |
+ if module is not None: |
+ return module |
+ if filepath is None or not is_python_source(filepath): |
+ try: |
+ module = load_module_from_name(modname) |
+ except Exception, ex: |
+ msg = 'Unable to load module %s (%s)' % (modname, ex) |
+ raise AstroidBuildingException(msg) |
+ return self.ast_from_module(module, modname) |
+ return self.ast_from_file(filepath, modname, fallback=False) |
+ finally: |
+ os.chdir(old_cwd) |
+ |
+ def zip_import_data(self, filepath): |
+ if zipimport is None: |
+ return None |
+ from astroid.builder import AstroidBuilder |
+ builder = AstroidBuilder(self) |
+ for ext in ('.zip', '.egg'): |
+ try: |
+ eggpath, resource = filepath.rsplit(ext + '/', 1) |
+ except ValueError: |
+ continue |
+ try: |
+ importer = zipimport.zipimporter(eggpath + ext) |
+ zmodname = resource.replace('/', '.') |
+ if importer.is_package(resource): |
+ zmodname = zmodname + '.__init__' |
+ module = builder.string_build(importer.get_source(resource), |
+ zmodname, filepath) |
+ return module |
+ except: |
+ continue |
+ return None |
+ |
+ def file_from_module_name(self, modname, contextfile): |
+ try: |
+ value = self._mod_file_cache[(modname, contextfile)] |
+ except KeyError: |
+ try: |
+ value = file_from_modpath(modname.split('.'), |
+ context_file=contextfile) |
+ except ImportError, ex: |
+ msg = 'Unable to load module %s (%s)' % (modname, ex) |
+ value = AstroidBuildingException(msg) |
+ self._mod_file_cache[(modname, contextfile)] = value |
+ if isinstance(value, AstroidBuildingException): |
+ raise value |
+ return value |
+ |
+ def ast_from_module(self, module, modname=None): |
+ """given an imported module, return the astroid object""" |
+ modname = modname or module.__name__ |
+ if modname in self.astroid_cache: |
+ return self.astroid_cache[modname] |
+ try: |
+ # some builtin modules don't have __file__ attribute |
+ filepath = module.__file__ |
+ if is_python_source(filepath): |
+ return self.ast_from_file(filepath, modname) |
+ except AttributeError: |
+ pass |
+ from astroid.builder import AstroidBuilder |
+ return AstroidBuilder(self).module_build(module, modname) |
+ |
+ def ast_from_class(self, klass, modname=None): |
+ """get astroid for the given class""" |
+ if modname is None: |
+ try: |
+ modname = klass.__module__ |
+ except AttributeError: |
+ raise AstroidBuildingException( |
+ 'Unable to get module for class %s' % safe_repr(klass)) |
+ modastroid = self.ast_from_module_name(modname) |
+ return modastroid.getattr(klass.__name__)[0] # XXX |
+ |
+ |
+ def infer_ast_from_something(self, obj, context=None): |
+ """infer astroid for the given class""" |
+ if hasattr(obj, '__class__') and not isinstance(obj, type): |
+ klass = obj.__class__ |
+ else: |
+ klass = obj |
+ try: |
+ modname = klass.__module__ |
+ except AttributeError: |
+ raise AstroidBuildingException( |
+ 'Unable to get module for %s' % safe_repr(klass)) |
+ except Exception, ex: |
+ raise AstroidBuildingException( |
+ 'Unexpected error while retrieving module for %s: %s' |
+ % (safe_repr(klass), ex)) |
+ try: |
+ name = klass.__name__ |
+ except AttributeError: |
+ raise AstroidBuildingException( |
+ 'Unable to get name for %s' % safe_repr(klass)) |
+ except Exception, ex: |
+ raise AstroidBuildingException( |
+ 'Unexpected error while retrieving name for %s: %s' |
+ % (safe_repr(klass), ex)) |
+ # take care, on living object __module__ is regularly wrong :( |
+ modastroid = self.ast_from_module_name(modname) |
+ if klass is obj: |
+ for infered in modastroid.igetattr(name, context): |
+ yield infered |
+ else: |
+ for infered in modastroid.igetattr(name, context): |
+ yield infered.instanciate_class() |
+ |
+ def project_from_files(self, files, func_wrapper=astroid_wrapper, |
+ project_name=None, black_list=None): |
+ """return a Project from a list of files or modules""" |
+ # build the project representation |
+ project_name = project_name or self.config.project |
+ black_list = black_list or self.config.black_list |
+ project = Project(project_name) |
+ for something in files: |
+ if not exists(something): |
+ fpath = file_from_modpath(something.split('.')) |
+ elif isdir(something): |
+ fpath = join(something, '__init__.py') |
+ else: |
+ fpath = something |
+ astroid = func_wrapper(self.ast_from_file, fpath) |
+ if astroid is None: |
+ continue |
+ # XXX why is first file defining the project.path ? |
+ project.path = project.path or astroid.file |
+ project.add_module(astroid) |
+ base_name = astroid.name |
+ # recurse in package except if __init__ was explicitly given |
+ if astroid.package and something.find('__init__') == -1: |
+ # recurse on others packages / modules if this is a package |
+ for fpath in get_module_files(dirname(astroid.file), |
+ black_list): |
+ astroid = func_wrapper(self.ast_from_file, fpath) |
+ if astroid is None or astroid.name == base_name: |
+ continue |
+ project.add_module(astroid) |
+ return project |
+ |
+ def register_transform(self, node_class, transform, predicate=None): |
+ """Register `transform(node)` function to be applied on the given |
+ Astroid's `node_class` if `predicate` is None or return a true value |
+ when called with the node as argument. |
+ |
+ The transform function may return a value which is then used to |
+ substitute the original node in the tree. |
+ """ |
+ self.transforms.setdefault(node_class, []).append((transform, predicate)) |
+ |
+ def unregister_transform(self, node_class, transform, predicate=None): |
+ """Unregister the given transform.""" |
+ self.transforms[node_class].remove((transform, predicate)) |
+ |
+ def transform(self, node): |
+ """Call matching transforms for the given node if any and return the |
+ transformed node. |
+ """ |
+ cls = node.__class__ |
+ if cls not in self.transforms: |
+ # no transform registered for this class of node |
+ return node |
+ |
+ transforms = self.transforms[cls] |
+ orig_node = node # copy the reference |
+ for transform_func, predicate in transforms: |
+ if predicate is None or predicate(node): |
+ ret = transform_func(node) |
+ # if the transformation function returns something, it's |
+ # expected to be a replacement for the node |
+ if ret is not None: |
+ if node is not orig_node: |
+ # node has already be modified by some previous |
+ # transformation, warn about it |
+ warn('node %s substituted multiple times' % node) |
+ node = ret |
+ return node |
+ |
+ def cache_module(self, module): |
+ """Cache a module if no module with the same name is known yet.""" |
+ self.astroid_cache.setdefault(module.name, module) |
+ |
+ def clear_cache(self): |
+ self.astroid_cache.clear() |
+ # force bootstrap again, else we may ends up with cache inconsistency |
+ # between the manager and CONST_PROXY, making |
+ # unittest_lookup.LookupTC.test_builtin_lookup fail depending on the |
+ # test order |
+ from astroid.raw_building import astroid_bootstrapping |
+ astroid_bootstrapping() |
+ |
+ |
+class Project(object): |
+ """a project handle a set of modules / packages""" |
+ def __init__(self, name=''): |
+ self.name = name |
+ self.path = None |
+ self.modules = [] |
+ self.locals = {} |
+ self.__getitem__ = self.locals.__getitem__ |
+ self.__iter__ = self.locals.__iter__ |
+ self.values = self.locals.values |
+ self.keys = self.locals.keys |
+ self.items = self.locals.items |
+ |
+ def add_module(self, node): |
+ self.locals[node.name] = node |
+ self.modules.append(node) |
+ |
+ def get_module(self, name): |
+ return self.locals[name] |
+ |
+ def get_children(self): |
+ return self.modules |
+ |
+ def __repr__(self): |
+ return '<Project %r at %s (%s modules)>' % (self.name, id(self), |
+ len(self.modules)) |
+ |
+ |