summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Vandenberg <jayvdb@gmail.com>2016-05-13 01:28:52 +0700
committerPhil Frost <indigo@bitglue.com>2016-05-12 14:28:52 -0400
commitd8591997d9718f482f7f1d39f2bf1aa0e4a5ab92 (patch)
treeecbc080d759d911b58a11815f356c5019397c316
parent885a8e5395f3eac58a995a49dbe347b8f6648f64 (diff)
downloadpyflakes-d8591997d9718f482f7f1d39f2bf1aa0e4a5ab92.tar.gz
Fix TypeError when processing relative imports (#61)
Fixes lp:1560134 aec68a784 added module names to error messages, however it caused a TypeError for relative imports that do not specify a module such as: from . import x This fixes the TypeError, and also adds the necessary leading dots for relative import error messages. Add tests for various types of relative imports.
-rw-r--r--pyflakes/checker.py23
-rw-r--r--pyflakes/test/test_imports.py105
2 files changed, 122 insertions, 6 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 9545cab..43acc69 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -209,7 +209,12 @@ class ImportationFrom(Importation):
def __init__(self, name, source, module, real_name=None):
self.module = module
self.real_name = real_name or name
- full_name = module + '.' + self.real_name
+
+ if module.endswith('.'):
+ full_name = module + self.real_name
+ else:
+ full_name = module + '.' + self.real_name
+
super(ImportationFrom, self).__init__(name, source, full_name)
def __str__(self):
@@ -244,7 +249,11 @@ class StarImportation(Importation):
return 'from ' + self.fullName + ' import *'
def __str__(self):
- return self.name
+ # When the module ends with a ., avoid the ambiguous '..*'
+ if self.fullName.endswith('.'):
+ return self.source_statement
+ else:
+ return self.name
class FutureImportation(ImportationFrom):
@@ -1142,6 +1151,8 @@ class Checker(object):
else:
self.futuresAllowed = False
+ module = ('.' * node.level) + (node.module or '')
+
for alias in node.names:
name = alias.asname or alias.name
if node.module == '__future__':
@@ -1153,15 +1164,15 @@ class Checker(object):
# Only Python 2, local import * is a SyntaxWarning
if not PY2 and not isinstance(self.scope, ModuleScope):
self.report(messages.ImportStarNotPermitted,
- node, node.module)
+ node, module)
continue
self.scope.importStarred = True
- self.report(messages.ImportStarUsed, node, node.module)
- importation = StarImportation(node.module, node)
+ self.report(messages.ImportStarUsed, node, module)
+ importation = StarImportation(module, node)
else:
importation = ImportationFrom(name, node,
- node.module, alias.name)
+ module, alias.name)
self.addBinding(node, importation)
def TRY(self, node):
diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py
index 9cbd4d7..f1bb5cd 100644
--- a/pyflakes/test/test_imports.py
+++ b/pyflakes/test/test_imports.py
@@ -40,6 +40,26 @@ class TestImportationObject(TestCase):
assert binding.source_statement == 'import a.b as a'
assert str(binding) == 'a.b as a'
+ def test_importfrom_relative(self):
+ binding = ImportationFrom('a', None, '.', 'a')
+ assert binding.source_statement == 'from . import a'
+ assert str(binding) == '.a'
+
+ def test_importfrom_relative_parent(self):
+ binding = ImportationFrom('a', None, '..', 'a')
+ assert binding.source_statement == 'from .. import a'
+ assert str(binding) == '..a'
+
+ def test_importfrom_relative_with_module(self):
+ binding = ImportationFrom('b', None, '..a', 'b')
+ assert binding.source_statement == 'from ..a import b'
+ assert str(binding) == '..a.b'
+
+ def test_importfrom_relative_with_module_as(self):
+ binding = ImportationFrom('c', None, '..a', 'b')
+ assert binding.source_statement == 'from ..a import b as c'
+ assert str(binding) == '..a.b as c'
+
def test_importfrom_member(self):
binding = ImportationFrom('b', None, 'a', 'b')
assert binding.source_statement == 'from a import b'
@@ -65,6 +85,11 @@ class TestImportationObject(TestCase):
assert binding.source_statement == 'from a.b import *'
assert str(binding) == 'a.b.*'
+ def test_importfrom_star_relative(self):
+ binding = StarImportation('.b', None)
+ assert binding.source_statement == 'from .b import *'
+ assert str(binding) == '.b.*'
+
def test_importfrom_future(self):
binding = FutureImportation('print_function', None, None)
assert binding.source_statement == 'from __future__ import print_function'
@@ -77,6 +102,29 @@ class Test(TestCase):
self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
+ def test_unusedImport_relative(self):
+ self.flakes('from . import fu', m.UnusedImport)
+ self.flakes('from . import fu as baz', m.UnusedImport)
+ self.flakes('from .. import fu', m.UnusedImport)
+ self.flakes('from ... import fu', m.UnusedImport)
+ self.flakes('from .. import fu as baz', m.UnusedImport)
+ self.flakes('from .bar import fu', m.UnusedImport)
+ self.flakes('from ..bar import fu', m.UnusedImport)
+ self.flakes('from ...bar import fu', m.UnusedImport)
+ self.flakes('from ...bar import fu as baz', m.UnusedImport)
+
+ checker = self.flakes('from . import fu', m.UnusedImport)
+
+ error = checker.messages[0]
+ assert error.message == '%r imported but unused'
+ assert error.message_args == ('.fu', )
+
+ checker = self.flakes('from . import fu as baz', m.UnusedImport)
+
+ error = checker.messages[0]
+ assert error.message == '%r imported but unused'
+ assert error.message_args == ('.fu as baz', )
+
def test_aliasedImport(self):
self.flakes('import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
@@ -94,6 +142,12 @@ class Test(TestCase):
self.flakes('from baz import fu; print(fu)')
self.flakes('import fu; del fu')
+ def test_usedImport_relative(self):
+ self.flakes('from . import fu; assert fu')
+ self.flakes('from .bar import fu; assert fu')
+ self.flakes('from .. import fu; assert fu')
+ self.flakes('from ..bar import fu as baz; assert baz')
+
def test_redefinedWhileUnused(self):
self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
@@ -687,6 +741,49 @@ class Test(TestCase):
pass
''', m.ImportStarUsed, m.UnusedImport)
+ checker = self.flakes('from fu import *',
+ m.ImportStarUsed, m.UnusedImport)
+
+ error = checker.messages[0]
+ assert error.message.startswith("'from %s import *' used; unable ")
+ assert error.message_args == ('fu', )
+
+ error = checker.messages[1]
+ assert error.message == '%r imported but unused'
+ assert error.message_args == ('fu.*', )
+
+ def test_importStar_relative(self):
+ """Use of import * from a relative import is reported."""
+ self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport)
+ self.flakes('''
+ try:
+ from .fu import *
+ except:
+ pass
+ ''', m.ImportStarUsed, m.UnusedImport)
+
+ checker = self.flakes('from .fu import *',
+ m.ImportStarUsed, m.UnusedImport)
+
+ error = checker.messages[0]
+ assert error.message.startswith("'from %s import *' used; unable ")
+ assert error.message_args == ('.fu', )
+
+ error = checker.messages[1]
+ assert error.message == '%r imported but unused'
+ assert error.message_args == ('.fu.*', )
+
+ checker = self.flakes('from .. import *',
+ m.ImportStarUsed, m.UnusedImport)
+
+ error = checker.messages[0]
+ assert error.message.startswith("'from %s import *' used; unable ")
+ assert error.message_args == ('..', )
+
+ error = checker.messages[1]
+ assert error.message == '%r imported but unused'
+ assert error.message_args == ('from .. import *', )
+
@skipIf(version_info < (3,),
'import * below module level is a warning on Python 2')
def test_localImportStar(self):
@@ -700,6 +797,14 @@ class Test(TestCase):
from fu import *
''', m.ImportStarNotPermitted)
+ checker = self.flakes('''
+ class a:
+ from .. import *
+ ''', m.ImportStarNotPermitted)
+ error = checker.messages[0]
+ assert error.message == "'from %s import *' only allowed at module level"
+ assert error.message_args == ('..', )
+
@skipIf(version_info > (3,),
'import * below module level is an error on Python 3')
def test_importStarNested(self):