From 5701ac1a10cbeefa7447277fb4008262185d394a Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Fri, 21 Jun 2013 17:02:17 +0200 Subject: [PATCH 01/20] Add absolute import detection --- jedi/parsing_representation.py | 28 ++++++++++++++++++++++++++++ test/base.py | 4 ++++ test/test_absolute_import.py | 27 +++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 test/test_absolute_import.py 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 From d32045303fd82f423afd943e423615e8bce97063 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Fri, 21 Jun 2013 19:25:18 +0200 Subject: [PATCH 02/20] Add absolute_import failing SSCCE, and get pytest to ignore it --- pytest.ini | 2 +- test/absolute_import/local_module.py | 13 +++++++++++++ test/absolute_import/minimal_demo.py | 9 +++++++++ test/absolute_import/unittest.py | 13 +++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 test/absolute_import/local_module.py create mode 100644 test/absolute_import/minimal_demo.py create mode 100644 test/absolute_import/unittest.py diff --git a/pytest.ini b/pytest.ini index b393f082..ab58a87e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,7 +2,7 @@ addopts = --doctest-modules # Ignore broken files in blackbox test directories -norecursedirs = .* docs completion refactor +norecursedirs = .* docs completion refactor absolute_import # Activate `clean_jedi_cache` fixture for all tests. This should be # fine as long as we are using `clean_jedi_cache` as a session scoped diff --git a/test/absolute_import/local_module.py b/test/absolute_import/local_module.py new file mode 100644 index 00000000..0064586e --- /dev/null +++ b/test/absolute_import/local_module.py @@ -0,0 +1,13 @@ +""" +This is a module that imports the *standard library* unittest, +despite there being a local "unittest" module. It specifies that it +wants the stdlib one with the ``absolute_import`` __future__ import. + +The twisted equivalent of this module is ``twisted.trial._synctest``. +""" +from __future__ import absolute_import + +import unittest # this is stdlib unittest, but jedi gets the local one + +class Assertions(unittest.TestCase): + pass diff --git a/test/absolute_import/minimal_demo.py b/test/absolute_import/minimal_demo.py new file mode 100644 index 00000000..6231e62a --- /dev/null +++ b/test/absolute_import/minimal_demo.py @@ -0,0 +1,9 @@ +import jedi + +filename = "unittest.py" +with open(filename) as f: + lines = f.readlines() +src = "".join(lines) +script = jedi.Script(src, len(lines), len(lines[1]), filename) + +print script.completions() diff --git a/test/absolute_import/unittest.py b/test/absolute_import/unittest.py new file mode 100644 index 00000000..d8957afc --- /dev/null +++ b/test/absolute_import/unittest.py @@ -0,0 +1,13 @@ +""" +This is a module that shadows a builtin (intentionally). + +It imports a local module, which in turn imports stdlib unittest (the +name shadowed by this module). If that is properly resolved, there's +no problem. However, if jedi doesn't understand absolute_imports, it +will get this module again, causing infinite recursion. +""" +from local_module import Assertions + +class TestCase(Assertions): + def test(self): + self.assertT From 38daa3cc3469bb43576618e8888ced100b4f78ff Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Fri, 21 Jun 2013 19:49:05 +0200 Subject: [PATCH 03/20] Add failing test case --- test/test_absolute_import.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index 0f5b79be..3f247a8e 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -1,3 +1,4 @@ +import jedi from jedi.parsing import Parser from jedi._compatibility import is_py3k; is_py3k # shut up pyflakes from . import base @@ -25,3 +26,12 @@ def test_imports_are_absolute_in_modules_with_future_import(): """ parser = Parser("from __future__ import absolute_import", "test.py") assert parser.scope.absolute_imports + +@base.cwd_at("test/absolute_import") +def test_can_complete_when_shadowing(): + filename = "unittest.py" + with open(filename) as f: + lines = f.readlines() + src = "".join(lines) + script = jedi.Script(src, len(lines), len(lines[1]), filename) + assert script.completions() From 0b3955a257e8709647910313e752dee616c356f1 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sat, 22 Jun 2013 08:53:51 +0200 Subject: [PATCH 04/20] Remove minimal_demo.py demo script --- test/absolute_import/minimal_demo.py | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 test/absolute_import/minimal_demo.py diff --git a/test/absolute_import/minimal_demo.py b/test/absolute_import/minimal_demo.py deleted file mode 100644 index 6231e62a..00000000 --- a/test/absolute_import/minimal_demo.py +++ /dev/null @@ -1,9 +0,0 @@ -import jedi - -filename = "unittest.py" -with open(filename) as f: - lines = f.readlines() -src = "".join(lines) -script = jedi.Script(src, len(lines), len(lines[1]), filename) - -print script.completions() From 397b881c96416096bc0b5c35c7371f5e947012a7 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sat, 22 Jun 2013 08:56:32 +0200 Subject: [PATCH 05/20] PEP8 whitespace --- test/test_absolute_import.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index 3f247a8e..469320ad 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -3,6 +3,7 @@ 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(): """ @@ -11,6 +12,7 @@ def test_py3k_imports_are_always_absolute(): parser = Parser("1", "test.py") assert parser.scope.absolute_imports + @base.py2_only def test_py2_imports_are_not_always_absolute(): """ @@ -19,6 +21,7 @@ def test_py2_imports_are_not_always_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 @@ -27,6 +30,7 @@ def test_imports_are_absolute_in_modules_with_future_import(): parser = Parser("from __future__ import absolute_import", "test.py") assert parser.scope.absolute_imports + @base.cwd_at("test/absolute_import") def test_can_complete_when_shadowing(): filename = "unittest.py" From ae94a2cda10d4e361019329decc677001f52c29b Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sat, 22 Jun 2013 08:57:02 +0200 Subject: [PATCH 06/20] Remove stale import --- test/test_absolute_import.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index 469320ad..d625b750 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -1,6 +1,5 @@ import jedi from jedi.parsing import Parser -from jedi._compatibility import is_py3k; is_py3k # shut up pyflakes from . import base From cb4acccfe5ba292689661cfb57bcf2f4a53da6b0 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sat, 22 Jun 2013 09:54:18 +0200 Subject: [PATCH 07/20] More PEP8, minor cleanup --- jedi/parsing_representation.py | 8 +++++--- test/absolute_import/local_module.py | 1 + test/absolute_import/unittest.py | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 378eaa48..1d563fcb 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -327,6 +327,7 @@ 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 @@ -335,13 +336,14 @@ def _enables_absolute_import(imp): 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. """ - pass + """ + For isinstance checks. fast_parser.Module also inherits from this. + """ class SubModule(Scope, Module): - """ The top scope, which is always a module. Depending on the underlying parser this may be a full module or just a part diff --git a/test/absolute_import/local_module.py b/test/absolute_import/local_module.py index 0064586e..d256fd67 100644 --- a/test/absolute_import/local_module.py +++ b/test/absolute_import/local_module.py @@ -9,5 +9,6 @@ from __future__ import absolute_import import unittest # this is stdlib unittest, but jedi gets the local one + class Assertions(unittest.TestCase): pass diff --git a/test/absolute_import/unittest.py b/test/absolute_import/unittest.py index d8957afc..eee1e937 100644 --- a/test/absolute_import/unittest.py +++ b/test/absolute_import/unittest.py @@ -8,6 +8,7 @@ will get this module again, causing infinite recursion. """ from local_module import Assertions + class TestCase(Assertions): def test(self): self.assertT From adec6669945234345ee42b708aab02628f1372d0 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 21:56:00 +0200 Subject: [PATCH 08/20] py.test skipping evaluation rules are kinda silly, and in this case completely useless --- test/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/base.py b/test/base.py index 450d3302..c9affeb4 100644 --- a/test/base.py +++ b/test/base.py @@ -62,5 +62,5 @@ def cwd_at(path): return wrapper return decorator -py3_only = pytest.mark.skipif("not is_py3k") -py2_only = pytest.mark.skipif("is_py3k") +py3_only = pytest.mark.skipif("sys.hexversion < 0x03000000") +py2_only = pytest.mark.skipif("sys.hexversion >= 0x03000000") From e82e3eaa0d7403084be3b08d3211a3daa89ce884 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 21:59:58 +0200 Subject: [PATCH 09/20] Don't assume every module has a namespace --- jedi/parsing_representation.py | 3 +++ test/test_absolute_import.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 1d563fcb..206feed1 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -333,6 +333,9 @@ def _enables_absolute_import(imp): Checks if the import is a ``__future__`` import that enables the ``absolute_import`` feature. """ + if imp.namespace is None: + return False + namespace, feature = imp.from_ns.names[0], imp.namespace.names[0] return namespace == "__future__" and feature == "absolute_import" diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index d625b750..bfa3b490 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -21,6 +21,16 @@ def test_py2_imports_are_not_always_absolute(): assert not parser.scope.absolute_imports +def test_dont_break_imports_without_namespaces(): + """ + The code checking for ``from __future__ import absolute_import`` shouldn't + assume that all imports have non-``None`` ``namespace`` attributes. + """ + src = "from __future__ import absolute_import\nimport xyzzy" + parser = Parser(src, "test.py") + assert parser.scope.absolute_imports + + def test_imports_are_absolute_in_modules_with_future_import(): """ In any module with the ``absolute_import`` ``__future__`` import, all From 7eff1bbf06ce775e4bfdee4d8de8dc0b928ceabd Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 22:00:23 +0200 Subject: [PATCH 10/20] Don't mess with the path if the import is absolute --- jedi/imports.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jedi/imports.py b/jedi/imports.py index 81669ba1..3c08c9a3 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -91,6 +91,11 @@ class ImportPath(pr.Base): and len(self.import_stmt.namespace.names) > 1 \ and not self.direct_resolve + @property + def is_absolute(self): + module = self.import_stmt.get_parent_until(pr.IsScope) + return module.absolute_imports + def get_nested_import(self, parent): """ See documentation of `self.is_nested_import`. @@ -252,7 +257,7 @@ class ImportPath(pr.Base): return importing - if self.file_path: + if self.file_path and not self.is_absolute: sys_path_mod = list(self.sys_path_with_modifications()) sys_path_mod.insert(0, self.file_path) else: From 58b165e4b60cee264d7cadc4694177229017aa31 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 22:02:37 +0200 Subject: [PATCH 11/20] Don't assume either namespace is not-None --- jedi/parsing_representation.py | 2 +- test/test_absolute_import.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 206feed1..59b11bca 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -333,7 +333,7 @@ def _enables_absolute_import(imp): Checks if the import is a ``__future__`` import that enables the ``absolute_import`` feature. """ - if imp.namespace is None: + if imp.from_ns is None or imp.namespace is None: return False namespace, feature = imp.from_ns.names[0], imp.namespace.names[0] diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index bfa3b490..343ce970 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -24,7 +24,7 @@ def test_py2_imports_are_not_always_absolute(): def test_dont_break_imports_without_namespaces(): """ The code checking for ``from __future__ import absolute_import`` shouldn't - assume that all imports have non-``None`` ``namespace`` attributes. + assume that all imports have non-``None`` namespaces. """ src = "from __future__ import absolute_import\nimport xyzzy" parser = Parser(src, "test.py") From 368c7fd5b561247917ccb1ccf0593f498b969932 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 22:30:53 +0200 Subject: [PATCH 12/20] Only deal with explicit absolute_import --- jedi/imports.py | 8 ++------ jedi/parsing_representation.py | 22 ++++++++++------------ test/test_absolute_import.py | 27 ++++++++------------------- 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/jedi/imports.py b/jedi/imports.py index 3c08c9a3..aa055795 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -91,11 +91,6 @@ class ImportPath(pr.Base): and len(self.import_stmt.namespace.names) > 1 \ and not self.direct_resolve - @property - def is_absolute(self): - module = self.import_stmt.get_parent_until(pr.IsScope) - return module.absolute_imports - def get_nested_import(self, parent): """ See documentation of `self.is_nested_import`. @@ -257,7 +252,8 @@ class ImportPath(pr.Base): return importing - if self.file_path and not self.is_absolute: + parent = self.import_stmt.get_parent_until() + if self.file_path and not parent.explicit_absolute_import: sys_path_mod = list(self.sys_path_with_modifications()) sys_path_mod.insert(0, self.file_path) else: diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 59b11bca..21cce274 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -155,6 +155,7 @@ class Scope(Simple, IsScope): # returns will be in "normal" modules. self.returns = [] self.is_generator = False + self._explicit_absolute_imports = None def add_scope(self, sub, decorators): sub.parent = self.use_as_parent @@ -295,26 +296,23 @@ class Scope(Simple, IsScope): return p @property - def absolute_imports(self): + def explicit_absolute_import(self): """ - Checks if imports in this scope are absolute. + Checks if imports in this scope are explicitly absolute, i.e. there + is a ``__future__`` import. - 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 will cause it to walk through all the imports in the + parse tree. - 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 + if self._explicit_absolute_imports is not None: + return self._explicit_absolute_imports has_import = any(_enables_absolute_import(i) for i in self.imports) - self._absolute_imports = has_import + self._explicit_absolute_imports = has_import return has_import - _absolute_imports = True if is_py3k else None - def __repr__(self): try: name = self.path diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index 343ce970..8d7e9e00 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -3,22 +3,20 @@ from jedi.parsing import Parser from . import base -@base.py3_only -def test_py3k_imports_are_always_absolute(): +def test_explicit_absolute_imports(): """ - By default, imports in Python 3 are absolute. + Detect modules with ``from __future__ import absolute_import``. """ - parser = Parser("1", "test.py") - assert parser.scope.absolute_imports + parser = Parser("from __future__ import absolute_import", "test.py") + assert parser.scope.explicit_absolute_import -@base.py2_only -def test_py2_imports_are_not_always_absolute(): +def test_no_explicit_absolute_imports(): """ - By default, imports in Python 2 are not absolute. + Detect modules without ``from __future__ import absolute_import``. """ parser = Parser("1", "test.py") - assert not parser.scope.absolute_imports + assert not parser.scope.explicit_absolute_import def test_dont_break_imports_without_namespaces(): @@ -28,16 +26,7 @@ def test_dont_break_imports_without_namespaces(): """ src = "from __future__ import absolute_import\nimport xyzzy" parser = Parser(src, "test.py") - assert 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 + assert parser.scope.explicit_absolute_import @base.cwd_at("test/absolute_import") From 60ee6c607a95c9192d836e7f01aa928b83390ef7 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 22:43:21 +0200 Subject: [PATCH 13/20] Don't throw away path optimization for modules with explicit absolute import --- jedi/imports.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jedi/imports.py b/jedi/imports.py index aa055795..d55d39e6 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -252,10 +252,13 @@ class ImportPath(pr.Base): return importing - parent = self.import_stmt.get_parent_until() - if self.file_path and not parent.explicit_absolute_import: + if self.file_path: sys_path_mod = list(self.sys_path_with_modifications()) - sys_path_mod.insert(0, self.file_path) + module = self.import_stmt.get_parent_until() + if not module.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) else: sys_path_mod = list(modules.get_sys_path()) From 3d4a71f6bb84fe4e5c7f51b109a55a7560ebb673 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 23 Jun 2013 22:50:38 +0200 Subject: [PATCH 14/20] Use Parser.module instead of Parser.scope --- test/test_absolute_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index 8d7e9e00..bb09582c 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -8,7 +8,7 @@ def test_explicit_absolute_imports(): Detect modules with ``from __future__ import absolute_import``. """ parser = Parser("from __future__ import absolute_import", "test.py") - assert parser.scope.explicit_absolute_import + assert parser.module.explicit_absolute_import def test_no_explicit_absolute_imports(): @@ -16,7 +16,7 @@ def test_no_explicit_absolute_imports(): Detect modules without ``from __future__ import absolute_import``. """ parser = Parser("1", "test.py") - assert not parser.scope.explicit_absolute_import + assert not parser.module.explicit_absolute_import def test_dont_break_imports_without_namespaces(): @@ -26,7 +26,7 @@ def test_dont_break_imports_without_namespaces(): """ src = "from __future__ import absolute_import\nimport xyzzy" parser = Parser(src, "test.py") - assert parser.scope.explicit_absolute_import + assert parser.module.explicit_absolute_import @base.cwd_at("test/absolute_import") From 42fc44b478635d81414a183873414940f04568ec Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Wed, 10 Jul 2013 12:06:13 +0200 Subject: [PATCH 15/20] Remove unused py.test test skipping decorators --- test/base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/base.py b/test/base.py index c9affeb4..09959898 100644 --- a/test/base.py +++ b/test/base.py @@ -61,6 +61,3 @@ def cwd_at(path): os.chdir(oldcwd) return wrapper return decorator - -py3_only = pytest.mark.skipif("sys.hexversion < 0x03000000") -py2_only = pytest.mark.skipif("sys.hexversion >= 0x03000000") From b5ee375fe11047fccc1ffdd7102379053be45363 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Wed, 10 Jul 2013 12:12:33 +0200 Subject: [PATCH 16/20] Rename explicit_absolute_import to has_explicit_absolute_import --- jedi/imports.py | 2 +- jedi/parsing_representation.py | 2 +- test/test_absolute_import.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jedi/imports.py b/jedi/imports.py index e6acb7f1..f0e5341f 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -255,7 +255,7 @@ class ImportPath(pr.Base): if self.file_path: sys_path_mod = list(self.sys_path_with_modifications()) module = self.import_stmt.get_parent_until() - if not module.explicit_absolute_import: + if not 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) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index e281c0f1..bddbb14d 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -296,7 +296,7 @@ class Scope(Simple, IsScope): return p @property - def explicit_absolute_import(self): + def has_explicit_absolute_import(self): """ Checks if imports in this scope are explicitly absolute, i.e. there is a ``__future__`` import. diff --git a/test/test_absolute_import.py b/test/test_absolute_import.py index bb09582c..1fe9aa9d 100644 --- a/test/test_absolute_import.py +++ b/test/test_absolute_import.py @@ -8,7 +8,7 @@ def test_explicit_absolute_imports(): Detect modules with ``from __future__ import absolute_import``. """ parser = Parser("from __future__ import absolute_import", "test.py") - assert parser.module.explicit_absolute_import + assert parser.module.has_explicit_absolute_import def test_no_explicit_absolute_imports(): @@ -16,7 +16,7 @@ def test_no_explicit_absolute_imports(): Detect modules without ``from __future__ import absolute_import``. """ parser = Parser("1", "test.py") - assert not parser.module.explicit_absolute_import + assert not parser.module.has_explicit_absolute_import def test_dont_break_imports_without_namespaces(): @@ -26,7 +26,7 @@ def test_dont_break_imports_without_namespaces(): """ src = "from __future__ import absolute_import\nimport xyzzy" parser = Parser(src, "test.py") - assert parser.module.explicit_absolute_import + assert parser.module.has_explicit_absolute_import @base.cwd_at("test/absolute_import") From 4068f5f05899c4485b3b7d50e60ec64882fcce99 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Wed, 10 Jul 2013 12:17:48 +0200 Subject: [PATCH 17/20] Move has_absolute_import to SubModule class --- jedi/parsing_representation.py | 35 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index bddbb14d..dace3af6 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -155,7 +155,6 @@ class Scope(Simple, IsScope): # returns will be in "normal" modules. self.returns = [] self.is_generator = False - self._explicit_absolute_imports = None def add_scope(self, sub, decorators): sub.parent = self.use_as_parent @@ -295,24 +294,6 @@ class Scope(Simple, IsScope): if p: return p - @property - def has_explicit_absolute_import(self): - """ - Checks if imports in this scope are explicitly absolute, i.e. there - is a ``__future__`` import. - - The result of this property is cached; the first time it is - called will cause it to walk through all the imports in the - parse tree. - - """ - if self._explicit_absolute_imports is not None: - return self._explicit_absolute_imports - - has_import = any(_enables_absolute_import(i) for i in self.imports) - self._explicit_absolute_imports = has_import - return has_import - def __repr__(self): try: name = self.path @@ -350,7 +331,6 @@ class SubModule(Scope, Module): Depending on the underlying parser this may be a full module or just a part of a module. """ - def __init__(self, path, start_pos=(1, 0), top_module=None): """ Initialize :class:`SubModule`. @@ -371,6 +351,8 @@ class SubModule(Scope, Module): self.use_as_parent = top_module or self + self._explicit_absolute_imports = None + def add_global(self, name): """ Global means in these context a function (subscope) which has a global @@ -409,6 +391,19 @@ class SubModule(Scope, Module): def is_builtin(self): return not (self.path is None or self.path.endswith('.py')) + @property + def has_explicit_absolute_import(self): + """ + Checks if imports in this module are explicitly absolute, i.e. there + is a ``__future__`` import. + """ + if self._explicit_absolute_imports is not None: + return self._explicit_absolute_imports + + has_import = any(_enables_absolute_import(i) for i in self.imports) + self._explicit_absolute_imports = has_import + return has_import + class Class(Scope): """ From 402bc092e3dc1d961c8fa202d3ce304ec40f5378 Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Wed, 10 Jul 2013 12:18:41 +0200 Subject: [PATCH 18/20] Remove has_explicit_absolute_import caching --- jedi/parsing_representation.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index dace3af6..2477df27 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -351,8 +351,6 @@ class SubModule(Scope, Module): self.use_as_parent = top_module or self - self._explicit_absolute_imports = None - def add_global(self, name): """ Global means in these context a function (subscope) which has a global @@ -397,12 +395,7 @@ class SubModule(Scope, Module): Checks if imports in this module are explicitly absolute, i.e. there is a ``__future__`` import. """ - if self._explicit_absolute_imports is not None: - return self._explicit_absolute_imports - - has_import = any(_enables_absolute_import(i) for i in self.imports) - self._explicit_absolute_imports = has_import - return has_import + return any(_enables_absolute_import(i) for i in self.imports) class Class(Scope): From e42d0d226fc212a375c3b656a079f996f1f8b79b Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Wed, 10 Jul 2013 12:24:02 +0200 Subject: [PATCH 19/20] Remove stale pytest import --- test/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/base.py b/test/base.py index 09959898..548e4cfe 100644 --- a/test/base.py +++ b/test/base.py @@ -6,7 +6,6 @@ else: import os from os.path import abspath, dirname import functools -import pytest import jedi From 96477cbb13cf728539a51c4b4220619964bc13ba Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Wed, 10 Jul 2013 12:24:19 +0200 Subject: [PATCH 20/20] Inline absolute import detection function --- jedi/parsing_representation.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 2477df27..e51132f0 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -307,18 +307,6 @@ class Scope(Simple, IsScope): 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. - """ - if imp.from_ns is None or imp.namespace is None: - return False - - 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. @@ -395,7 +383,15 @@ class SubModule(Scope, Module): Checks if imports in this module are explicitly absolute, i.e. there is a ``__future__`` import. """ - return any(_enables_absolute_import(i) for i in self.imports) + for imp in self.imports: + if imp.from_ns is None or imp.namespace is None: + continue + + namespace, feature = imp.from_ns.names[0], imp.namespace.names[0] + if namespace == "__future__" and feature == "absolute_import": + return True + + return False class Class(Scope):