1
0
forked from VimPlug/jedi

Merge pull request #252 from lvh/master

Handle explicit absolute imports correctly
This commit is contained in:
David Halter
2013-07-10 05:02:14 -07:00
6 changed files with 92 additions and 6 deletions

View File

@@ -254,6 +254,10 @@ 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.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)
else:
sys_path_mod = list(modules.get_sys_path())

View File

@@ -308,18 +308,17 @@ class Scope(Simple, IsScope):
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
of a module.
"""
def __init__(self, path, start_pos=(1, 0), top_module=None):
"""
Initialize :class:`SubModule`.
@@ -378,6 +377,22 @@ 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.
"""
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):
"""

View File

@@ -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

View File

@@ -0,0 +1,14 @@
"""
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

View File

@@ -0,0 +1,14 @@
"""
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

View File

@@ -0,0 +1,39 @@
import jedi
from jedi.parsing import Parser
from . import base
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.has_explicit_absolute_import
def test_no_explicit_absolute_imports():
"""
Detect modules without ``from __future__ import absolute_import``.
"""
parser = Parser("1", "test.py")
assert not parser.module.has_explicit_absolute_import
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`` namespaces.
"""
src = "from __future__ import absolute_import\nimport xyzzy"
parser = Parser(src, "test.py")
assert parser.module.has_explicit_absolute_import
@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()