Skip to content

Commit

Permalink
Fix TypeError when processing relative imports
Browse files Browse the repository at this point in the history
Fixes lp:1560134

aec68a7 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.
  • Loading branch information
jayvdb committed May 6, 2016
1 parent 29914fc commit c87dad4
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 6 deletions.
23 changes: 17 additions & 6 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -244,7 +249,11 @@ def source_statement(self):
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):
Expand Down Expand Up @@ -1142,6 +1151,8 @@ def IMPORTFROM(self, node):
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__':
Expand All @@ -1153,15 +1164,15 @@ def IMPORTFROM(self, node):
# 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):
Expand Down
105 changes: 105 additions & 0 deletions pyflakes/test/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ def test_import_submodule_as_source_name(self):
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'
Expand All @@ -65,6 +85,11 @@ def test_importfrom_star(self):
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'
Expand All @@ -77,6 +102,29 @@ def test_unusedImport(self):
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)
Expand All @@ -94,6 +142,12 @@ def test_usedImport(self):
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)
Expand Down Expand Up @@ -687,6 +741,49 @@ def test_importStar(self):
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):
Expand All @@ -700,6 +797,14 @@ class a:
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):
Expand Down

0 comments on commit c87dad4

Please sign in to comment.