| Index: third_party/logilab/astroid/manager.py
|
| diff --git a/third_party/logilab/astroid/manager.py b/third_party/logilab/astroid/manager.py
|
| index fde52e2b138c5a0eb59d42125e99416b263799e7..f5f1d79f40c605c31a21a55429f01e7f000c6467 100644
|
| --- a/third_party/logilab/astroid/manager.py
|
| +++ b/third_party/logilab/astroid/manager.py
|
| @@ -19,29 +19,31 @@
|
| possible by providing a class responsible to get astroid representation
|
| from various source and using a cache of built modules)
|
| """
|
| +from __future__ import print_function
|
|
|
| __docformat__ = "restructuredtext en"
|
|
|
| +import collections
|
| +import imp
|
| import os
|
| from os.path import dirname, join, isdir, exists
|
| from warnings import warn
|
| +import zipimport
|
|
|
| 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
|
| +from astroid import modutils
|
|
|
|
|
| def astroid_wrapper(func, modname):
|
| """wrapper to give to AstroidManager.project_from_files"""
|
| - print 'parsing %s...' % modname
|
| + print('parsing %s...' % modname)
|
| try:
|
| return func(modname)
|
| - except AstroidBuildingException, exc:
|
| - print exc
|
| - except Exception, exc:
|
| + except AstroidBuildingException as exc:
|
| + print(exc)
|
| + except Exception as exc:
|
| import traceback
|
| traceback.print_exc()
|
|
|
| @@ -85,18 +87,21 @@ class AstroidManager(OptionsProviderMixIn):
|
| # NOTE: cache entries are added by the [re]builder
|
| self.astroid_cache = {}
|
| self._mod_file_cache = {}
|
| - self.transforms = {}
|
| + self.transforms = collections.defaultdict(list)
|
| + self._failed_import_hooks = []
|
| + self.always_load_extensions = False
|
| + self.extension_package_whitelist = set()
|
|
|
| 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)
|
| + filepath = modutils.get_source_file(filepath, include_no_ext=True)
|
| source = True
|
| - except NoSourceFile:
|
| + except modutils.NoSourceFile:
|
| pass
|
| if modname is None:
|
| try:
|
| - modname = '.'.join(modpath_from_file(filepath))
|
| + modname = '.'.join(modutils.modpath_from_file(filepath))
|
| except ImportError:
|
| modname = filepath
|
| if modname in self.astroid_cache and self.astroid_cache[modname].file == filepath:
|
| @@ -109,30 +114,56 @@ class AstroidManager(OptionsProviderMixIn):
|
| raise AstroidBuildingException('unable to get astroid for file %s' %
|
| filepath)
|
|
|
| + def _build_stub_module(self, modname):
|
| + from astroid.builder import AstroidBuilder
|
| + return AstroidBuilder(self).string_build('', modname)
|
| +
|
| + def _can_load_extension(self, modname):
|
| + if self.always_load_extensions:
|
| + return True
|
| + if modutils.is_standard_module(modname):
|
| + return True
|
| + parts = modname.split('.')
|
| + return any(
|
| + '.'.join(parts[:x]) in self.extension_package_whitelist
|
| + for x in range(1, len(parts) + 1))
|
| +
|
| 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)
|
| + return self._build_stub_module(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):
|
| + filepath, mp_type = self.file_from_module_name(modname, context_file)
|
| + if mp_type == modutils.PY_ZIPMODULE:
|
| module = self.zip_import_data(filepath)
|
| if module is not None:
|
| return module
|
| - if filepath is None or not is_python_source(filepath):
|
| + elif mp_type in (imp.C_BUILTIN, imp.C_EXTENSION):
|
| + if mp_type == imp.C_EXTENSION and not self._can_load_extension(modname):
|
| + return self._build_stub_module(modname)
|
| try:
|
| - module = load_module_from_name(modname)
|
| - except Exception, ex:
|
| + module = modutils.load_module_from_name(modname)
|
| + except Exception as ex:
|
| msg = 'Unable to load module %s (%s)' % (modname, ex)
|
| raise AstroidBuildingException(msg)
|
| return self.ast_from_module(module, modname)
|
| + elif mp_type == imp.PY_COMPILED:
|
| + raise AstroidBuildingException("Unable to load compiled module %s" % (modname,))
|
| + if filepath is None:
|
| + raise AstroidBuildingException("Unable to load module %s" % (modname,))
|
| return self.ast_from_file(filepath, modname, fallback=False)
|
| + except AstroidBuildingException as e:
|
| + for hook in self._failed_import_hooks:
|
| + try:
|
| + return hook(modname)
|
| + except AstroidBuildingException:
|
| + pass
|
| + raise e
|
| finally:
|
| os.chdir(old_cwd)
|
|
|
| @@ -143,12 +174,12 @@ class AstroidManager(OptionsProviderMixIn):
|
| builder = AstroidBuilder(self)
|
| for ext in ('.zip', '.egg'):
|
| try:
|
| - eggpath, resource = filepath.rsplit(ext + '/', 1)
|
| + eggpath, resource = filepath.rsplit(ext + os.path.sep, 1)
|
| except ValueError:
|
| continue
|
| try:
|
| importer = zipimport.zipimporter(eggpath + ext)
|
| - zmodname = resource.replace('/', '.')
|
| + zmodname = resource.replace(os.path.sep, '.')
|
| if importer.is_package(resource):
|
| zmodname = zmodname + '.__init__'
|
| module = builder.string_build(importer.get_source(resource),
|
| @@ -163,9 +194,9 @@ class AstroidManager(OptionsProviderMixIn):
|
| value = self._mod_file_cache[(modname, contextfile)]
|
| except KeyError:
|
| try:
|
| - value = file_from_modpath(modname.split('.'),
|
| - context_file=contextfile)
|
| - except ImportError, ex:
|
| + value = modutils.file_info_from_modpath(
|
| + modname.split('.'), context_file=contextfile)
|
| + except ImportError as ex:
|
| msg = 'Unable to load module %s (%s)' % (modname, ex)
|
| value = AstroidBuildingException(msg)
|
| self._mod_file_cache[(modname, contextfile)] = value
|
| @@ -181,7 +212,7 @@ class AstroidManager(OptionsProviderMixIn):
|
| try:
|
| # some builtin modules don't have __file__ attribute
|
| filepath = module.__file__
|
| - if is_python_source(filepath):
|
| + if modutils.is_python_source(filepath):
|
| return self.ast_from_file(filepath, modname)
|
| except AttributeError:
|
| pass
|
| @@ -211,7 +242,7 @@ class AstroidManager(OptionsProviderMixIn):
|
| except AttributeError:
|
| raise AstroidBuildingException(
|
| 'Unable to get module for %s' % safe_repr(klass))
|
| - except Exception, ex:
|
| + except Exception as ex:
|
| raise AstroidBuildingException(
|
| 'Unexpected error while retrieving module for %s: %s'
|
| % (safe_repr(klass), ex))
|
| @@ -220,7 +251,7 @@ class AstroidManager(OptionsProviderMixIn):
|
| except AttributeError:
|
| raise AstroidBuildingException(
|
| 'Unable to get name for %s' % safe_repr(klass))
|
| - except Exception, ex:
|
| + except Exception as ex:
|
| raise AstroidBuildingException(
|
| 'Unexpected error while retrieving name for %s: %s'
|
| % (safe_repr(klass), ex))
|
| @@ -242,7 +273,7 @@ class AstroidManager(OptionsProviderMixIn):
|
| project = Project(project_name)
|
| for something in files:
|
| if not exists(something):
|
| - fpath = file_from_modpath(something.split('.'))
|
| + fpath = modutils.file_from_modpath(something.split('.'))
|
| elif isdir(something):
|
| fpath = join(something, '__init__.py')
|
| else:
|
| @@ -257,8 +288,8 @@ class AstroidManager(OptionsProviderMixIn):
|
| # 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):
|
| + for fpath in modutils.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
|
| @@ -267,18 +298,28 @@ class AstroidManager(OptionsProviderMixIn):
|
|
|
| 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
|
| + Astroid's `node_class` if `predicate` is None or returns true
|
| 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))
|
| + self.transforms[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 register_failed_import_hook(self, hook):
|
| + """"Registers a hook to resolve imports that cannot be found otherwise.
|
| +
|
| + `hook` must be a function that accepts a single argument `modname` which
|
| + contains the name of the module or package that could not be imported.
|
| + If `hook` can resolve the import, must return a node of type `astroid.Module`,
|
| + otherwise, it must raise `AstroidBuildingException`.
|
| + """
|
| + self._failed_import_hooks.append(hook)
|
| +
|
| def transform(self, node):
|
| """Call matching transforms for the given node if any and return the
|
| transformed node.
|
| @@ -308,6 +349,7 @@ class AstroidManager(OptionsProviderMixIn):
|
| self.astroid_cache.setdefault(module.name, module)
|
|
|
| def clear_cache(self):
|
| + # XXX clear transforms
|
| self.astroid_cache.clear()
|
| # force bootstrap again, else we may ends up with cache inconsistency
|
| # between the manager and CONST_PROXY, making
|
|
|