diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 78cfb298..1ba16531 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -284,7 +284,7 @@ class Script(object): if eval_stmt is None: return [] - module = self._parser.module() + module = self._evaluator.wrap(self._parser.module()) names, level, _, _ = helpers.check_error_statements(module, self._pos) if names: names = [str(n) for n in names] diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 9bbce465..79ecb756 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -74,6 +74,9 @@ class CompiledObject(Base): def py__bool__(self): return bool(self.obj) + def py__file__(self): + return self.obj.__file__ + def is_class(self): return inspect.isclass(self.obj) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index ccd9c7eb..ca81e5ec 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -23,7 +23,7 @@ from jedi import debug from jedi import cache from jedi.parser import fast from jedi.parser import tree as pr -from jedi.evaluate.sys_path import get_sys_path, sys_path_with_modifications +from jedi.evaluate import sys_path from jedi.evaluate import helpers from jedi import settings from jedi.common import source_to_unicode @@ -213,9 +213,11 @@ class _Importer(object): self._evaluator = evaluator self.level = level self.module = module - path = module.path - # TODO abspath - self.file_path = os.path.dirname(path) if path is not None else None + try: + self.file_path = module.py__file__() + except AttributeError: + # Can be None for certain compiled modules like 'builtins'. + self.file_path = None if level: base = module.py__package__().split('.') @@ -235,15 +237,13 @@ class _Importer(object): else: _add_error(self._evaluator, import_path[-1]) import_path = [] - - # TODO add import error. - debug.warning('Attempted relative import beyond top-level package.') + # TODO add import error. + debug.warning('Attempted relative import beyond top-level package.') else: # Here we basically rewrite the level to 0. import_path = tuple(base) + import_path self.import_path = import_path - @property def str_import_path(self): """Returns the import path as pure strings instead of `Name`.""" @@ -258,31 +258,20 @@ class _Importer(object): @memoize_default() def sys_path_with_modifications(self): in_path = [] - sys_path_mod = list(sys_path_with_modifications(self._evaluator, self.module)) + sys_path_mod = list(sys_path.sys_path_with_modifications(self._evaluator, self.module)) if self.file_path is not None: # If you edit e.g. gunicorn, there will be imports like this: # `from gunicorn import something`. But gunicorn is not in the # sys.path. Therefore look if gunicorn is a parent directory, #56. if self.import_path: # TODO is this check really needed? - parts = self.file_path.split(os.path.sep) - for i, p in enumerate(parts): - if p == unicode(self.import_path[0]): - new = os.path.sep.join(parts[:i]) - in_path.append(new) + for path in sys_path.traverse_parents(self.file_path): + if os.path.basename(path) == self.str_import_path[0]: + in_path.append(os.path.dirname(path)) - if not self.module.has_explicit_absolute_import: - # If the module explicitly asks for absolute imports, - # there's probably a bogus local one. - sys_path_mod.insert(0, self.file_path) - - # First the sys path is searched normally and if that doesn't - # succeed, try to search the parent directories, because sometimes - # Jedi doesn't recognize sys.path modifications (like py.test - # stuff). - old_path, temp_path = self.file_path, os.path.dirname(self.file_path) - while old_path != temp_path: - sys_path_mod.append(temp_path) - old_path, temp_path = temp_path, os.path.dirname(temp_path) + # Since we know nothing about the call location of the sys.path, + # it's a possibility that the current directory is the origin of + # the Python execution. + sys_path_mod.insert(0, os.path.dirname(self.file_path)) return in_path + sys_path_mod diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index c1eb07e2..27473481 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -760,6 +760,12 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): return name def py__file__(self): + """ + In contrast to Python's __file__ can be None. + """ + if self._module.path is None: + return None + return os.path.abspath(self._module.path) def py__package__(self): diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index c0e21299..2a4c74e6 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -188,7 +188,7 @@ def _get_paths_from_buildout_script(evaluator, buildout_script): yield path -def _traverse_parents(path): +def traverse_parents(path): while True: new = os.path.dirname(path) if new == path: @@ -198,7 +198,7 @@ def _traverse_parents(path): def _get_parent_dir_with_file(path, filename): - for parent in _traverse_parents(path): + for parent in traverse_parents(path): if os.path.isfile(os.path.join(parent, filename)): return parent return None @@ -208,7 +208,7 @@ def _detect_django_path(module_path): """ Detects the path of the very well known Django library (if used) """ result = [] - for parent in _traverse_parents(module_path): + for parent in traverse_parents(module_path): with common.ignored(IOError): with open(parent + os.path.sep + 'manage.py'): debug.dbg('Found django path: %s', module_path) diff --git a/test/test_evaluate/not_in_sys_path/pkg/__init__.py b/test/test_evaluate/not_in_sys_path/pkg/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test_evaluate/not_in_sys_path/pkg/module.py b/test/test_evaluate/not_in_sys_path/pkg/module.py index c2d3b682..53c44ca1 100644 --- a/test/test_evaluate/not_in_sys_path/pkg/module.py +++ b/test/test_evaluate/not_in_sys_path/pkg/module.py @@ -1,6 +1,6 @@ -import not_in_sys_path -import not_in_sys_path_package -from not_in_sys_path_package import module +from not_in_sys_path import not_in_sys_path +from not_in_sys_path import not_in_sys_path_package +from not_in_sys_path.not_in_sys_path_package import module not_in_sys_path.value not_in_sys_path_package.value