diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 52edd4c7..378eaa48 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -294,6 +294,27 @@ class Scope(Simple, IsScope): if p: return p + @property + def absolute_imports(self): + """ + Checks if imports in this scope are absolute. + + In Python 3, this is always true. In Python 2, this is true if there + is a ``absolute_import`` ``__future__`` import. + + The result of this property is cached; the first time it is called on + Python 2 will cause it to walk through all the imports in the parse + tree. + """ + if self._absolute_imports is not None: + return self._absolute_imports + + has_import = any(_enables_absolute_import(i) for i in self.imports) + self._absolute_imports = has_import + return has_import + + _absolute_imports = True if is_py3k else None + def __repr__(self): try: name = self.path @@ -306,6 +327,13 @@ class Scope(Simple, IsScope): return "<%s: %s@%s-%s>" % (type(self).__name__, name, self.start_pos[0], self.end_pos[0]) +def _enables_absolute_import(imp): + """ + Checks if the import is a ``__future__`` import that enables the + ``absolute_import`` feature. + """ + namespace, feature = imp.from_ns.names[0], imp.namespace.names[0] + return namespace == "__future__" and feature == "absolute_import" class Module(IsScope): """ For isinstance checks. fast_parser.Module also inherits from this. """ diff --git a/test/base.py b/test/base.py index 548e4cfe..450d3302 100644 --- a/test/base.py +++ b/test/base.py @@ -6,6 +6,7 @@ else: import os from os.path import abspath, dirname import functools +import pytest import jedi @@ -60,3 +61,6 @@ def cwd_at(path): os.chdir(oldcwd) return wrapper return decorator + +py3_only = pytest.mark.skipif("not is_py3k") +py2_only = pytest.mark.skipif("is_py3k") diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py new file mode 100644 index 00000000..0f5b79be --- /dev/null +++ b/test/test_absolute_import.py @@ -0,0 +1,27 @@ +from jedi.parsing import Parser +from jedi._compatibility import is_py3k; is_py3k # shut up pyflakes +from . import base + +@base.py3_only +def test_py3k_imports_are_always_absolute(): + """ + By default, imports in Python 3 are absolute. + """ + parser = Parser("1", "test.py") + assert parser.scope.absolute_imports + +@base.py2_only +def test_py2_imports_are_not_always_absolute(): + """ + By default, imports in Python 2 are not absolute. + """ + parser = Parser("1", "test.py") + assert not parser.scope.absolute_imports + +def test_imports_are_absolute_in_modules_with_future_import(): + """ + In any module with the ``absolute_import`` ``__future__`` import, all + imports are absolute. + """ + parser = Parser("from __future__ import absolute_import", "test.py") + assert parser.scope.absolute_imports