diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 557c6a1b..c0142f78 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -286,7 +286,7 @@ class Script(object): if new_name.start_pos is None: found_builtin = True - if found_builtin and not isinstance(name, imports.SubModuleName): + if found_builtin: yield name else: for new_name in new_names: @@ -305,7 +305,7 @@ class Script(object): return name.is_import() else: def check(name): - return isinstance(name, imports.SubModuleName) + return False names = filter_follow_imports(names, check) names = try_stub_to_actual_names(names, prefer_stub_to_compiled=True) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 24868a61..b03c6728 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -64,15 +64,22 @@ class BaseDefinition(object): """ self.is_keyword = isinstance(self._name, KeywordName) - # generate a path to the definition - self._module = name.get_root_context() + @memoize_method + def _get_module(self): + # This can take a while to complete, because in the worst case of + # imports (consider `import a` completions), we need to load all + # modules starting with a first. + return self._name.get_root_context() + + @property + def module_path(self): + """Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``""" try: - py__file__ = self._module.py__file__ + py__file__ = self._get_module().py__file__ except AttributeError: - self.module_path = None + return None else: - self.module_path = py__file__() - """Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``""" + return py__file__() @property def name(self): @@ -203,14 +210,14 @@ class BaseDefinition(object): >>> print(d.module_name) # doctest: +ELLIPSIS json """ - return self._module.name.string_name + return self._get_module().name.string_name def in_builtin_module(self): """Whether this is a builtin module.""" - if isinstance(self._module, StubModuleContext): + if isinstance(self._get_module(), StubModuleContext): return any(isinstance(context, compiled.CompiledObject) - for context in self._module.non_stub_context_set) - return isinstance(self._module, compiled.CompiledObject) + for context in self._get_module().non_stub_context_set) + return isinstance(self._get_module(), compiled.CompiledObject) @property def line(self): diff --git a/jedi/evaluate/names.py b/jedi/evaluate/names.py index 8fbae523..56a782af 100644 --- a/jedi/evaluate/names.py +++ b/jedi/evaluate/names.py @@ -4,6 +4,7 @@ from parso.tree import search_ancestor from jedi._compatibility import Parameter from jedi.evaluate.base_context import ContextSet +from jedi.cache import memoize_method class AbstractNameDefinition(object): @@ -139,25 +140,28 @@ class ImportName(AbstractNameDefinition): _level = 0 def __init__(self, parent_context, string_name): - self.parent_context = parent_context + self._from_module_context = parent_context self.string_name = string_name + @property + def parent_context(self): + m = self._from_module_context + import_contexts = self.infer() + if not import_contexts: + return m + # It's almost always possible to find the import or to not find it. The + # importing returns only one context, pretty much always. + return next(iter(import_contexts)) + + @memoize_method def infer(self): from jedi.evaluate.imports import Importer - return Importer( - self.parent_context.evaluator, - [self.string_name], - self.parent_context, - level=self._level, - ).follow() + m = self._from_module_context + return Importer(m.evaluator, [self.string_name], m, level=self._level).follow() def goto(self): return [m.name for m in self.infer()] - def get_root_context(self): - # Not sure if this is correct. - return self.parent_context.get_root_context() - @property def api_type(self): return 'module'