diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index f440762f..383e0198 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -81,6 +81,19 @@ class ImportWrapper(pr.Base): try: module = self._evaluator.wrap(self._import.get_parent_until()) import_path = self._import.path_for_name(self._name) + from_import_name = None + try: + from_names = self._import.get_from_names() + except AttributeError: + # Is an import_name + pass + else: + if len(from_names) + 1 == len(import_path): + # We have to fetch the from_names part first and then check + # if from_names exists in the modules. + from_import_name = import_path[-1] + import_path = from_names + importer = get_importer(self._evaluator, tuple(import_path), module, self._import.level) try: @@ -90,34 +103,50 @@ class ImportWrapper(pr.Base): return [] if module is None: + # TODO does that really happen? Why? return [] #if self._import.is_nested() and not self.nested_resolve: # scopes = [NestedImportModule(module, self._import)] - scopes = [module] + types = [module] + + if from_import_name is not None: + types = list(chain.from_iterable( + self._evaluator.find_types(s, from_import_name, is_goto) + for s in types)) + if not types: + importer = get_importer(self._evaluator, tuple(import_path), + module, self._import.level) + module, _ = importer.follow_file_system() + if module is None: + types = [] + else: + types = [module] + + # goto only accepts `Name` if is_goto and not rest: - scopes = [s.name for s in scopes] + types = [s.name for s in types] # follow the rest of the import (not FS -> classes, functions) if rest: if is_goto: - scopes = list(chain.from_iterable( + types = list(chain.from_iterable( self._evaluator.find_types(s, rest[0], is_goto=True) - for s in scopes)) + for s in types)) else: if self._import.type == 'import_from' \ or importer.str_import_path == ('os', 'path'): - scopes = importer.follow_rest(scopes[0], rest) + types = importer.follow_rest(types[0], rest) else: - scopes = [] - debug.dbg('after import: %s', scopes) - if not scopes: - analysis.add(self._evaluator, 'import-error', importer.import_path[-1]) + types = [] + debug.dbg('after import: %s', types) + #if not types: + # analysis.add(self._evaluator, 'import-error', importer.import_path[-1]) finally: self._evaluator.recursion_detector.pop_stmt() - return scopes + return types class NestedImportModule(pr.Module): @@ -158,7 +187,7 @@ def get_importer(evaluator, import_path, module, level=0): cache and speeds up libraries like ``numpy``. """ if level: - base = module.py__name__().split('.') + base = module.py__package__().split('.') if level > len(base): # TODO add import error. debug.warning('Attempted relative import beyond top-level package.') @@ -171,6 +200,7 @@ def get_importer(evaluator, import_path, module, level=0): check_import_path = tuple(unicode(i) for i in import_path) try: + print(check_import_path) return evaluator.import_cache[check_import_path] except KeyError: importer = _Importer(evaluator, import_path, module, level=0) @@ -452,7 +482,8 @@ class _Importer(object): paths = base.py__path__() except AttributeError: # The module is not a package. - raise NotImplementedError + analysis.add(self._evaluator, 'import-error', import_path[-1]) + return None else: debug.dbg('search_module %s in paths %s', module_name, paths) for path in paths: diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index a6fb5d7f..35bd9c4f 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -31,6 +31,7 @@ __ import os import pkgutil import imp +import re from itertools import chain from jedi._compatibility import use_metaclass, unicode, Python3Method @@ -760,6 +761,14 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): def name(self): return helpers.FakeName(unicode(self.base.name), self, (1, 0)) + def _get_init_directory(self): + for suffix, _, _ in imp.get_suffixes(): + ending = '__init__' + suffix + if self.py__file__().endswith(ending): + # Remove the ending, including the separator. + return self.py__file__()[:-len(ending) - 1] + return None + def py__name__(self): for name, module in self._evaluator.modules.items(): if module == self: @@ -768,6 +777,12 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): def py__file__(self): return self._module.path + def py__package__(self): + if self._get_init_directory() is None: + return re.sub(r'\.?[^\.]+$', '', self.py__name__()) + else: + return self.py__name__() + @property def py__path__(self): """ @@ -778,14 +793,12 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): def return_value(): return [path] - for suffix, _, _ in imp.get_suffixes(): - ending = '__init__' + suffix - if self.py__file__().endswith(ending): - # Remove the ending, including the separator. - path = self.py__file__()[:-len(ending) - 1] - return return_value - else: + path = self._get_init_directory() + + if path is None: raise AttributeError('Only packages have __path__ attributes.') + else: + return return_value @memoize_default() def _sub_modules_dict(self): diff --git a/jedi/parser/tree.py b/jedi/parser/tree.py index 1c6faf0a..619067e1 100644 --- a/jedi/parser/tree.py +++ b/jedi/parser/tree.py @@ -948,6 +948,17 @@ class ImportFrom(Import): return dict((alias, name) for name, alias in self._as_name_tuples() if alias is not None) + def get_from_names(self): + for n in self.children[1:]: + if n not in ('.', '...'): + break + if is_node(n, 'dotted_name'): # from x.y import + return n.children[::2] + elif n == 'import': # from . import + return [] + else: # from x import + return [n] + @property def level(self): """The level parameter of ``__import__``.""" @@ -987,15 +998,7 @@ class ImportFrom(Import): The import paths defined in an import statement. Typically an array like this: ``[, ]``. """ - for n in self.children[1:]: - if n not in ('.', '...'): - break - if is_node(n, 'dotted_name'): # from x.y import - dotted = n.children[::2] - elif n == 'import': # from . import - dotted = [] - else: # from x import - dotted = [n] + dotted = self.get_from_names() if self.children[-1] == '*': return [dotted] diff --git a/test/completion/imports.py b/test/completion/imports.py index fa58c57c..0b580f1f 100644 --- a/test/completion/imports.py +++ b/test/completion/imports.py @@ -119,8 +119,7 @@ def test_import_priorities(): See also #536. """ from import_tree import the_pkg, invisible_pkg - # TODO currently not enabled, later we should actually fix this. - ##? int() + #? int() invisible_pkg # The renamed invisible_pkg in that module (look at ``__init__.py``!) #? float()