Skip to content

Commit d859199

Browse files
jayvdbbitglue
authored andcommitted
Fix TypeError when processing relative imports (#61)
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.
1 parent 885a8e5 commit d859199

File tree

2 files changed

+122
-6
lines changed

2 files changed

+122
-6
lines changed

pyflakes/checker.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,12 @@ class ImportationFrom(Importation):
209209
def __init__(self, name, source, module, real_name=None):
210210
self.module = module
211211
self.real_name = real_name or name
212-
full_name = module + '.' + self.real_name
212+
213+
if module.endswith('.'):
214+
full_name = module + self.real_name
215+
else:
216+
full_name = module + '.' + self.real_name
217+
213218
super(ImportationFrom, self).__init__(name, source, full_name)
214219

215220
def __str__(self):
@@ -244,7 +249,11 @@ def source_statement(self):
244249
return 'from ' + self.fullName + ' import *'
245250

246251
def __str__(self):
247-
return self.name
252+
# When the module ends with a ., avoid the ambiguous '..*'
253+
if self.fullName.endswith('.'):
254+
return self.source_statement
255+
else:
256+
return self.name
248257

249258

250259
class FutureImportation(ImportationFrom):
@@ -1142,6 +1151,8 @@ def IMPORTFROM(self, node):
11421151
else:
11431152
self.futuresAllowed = False
11441153

1154+
module = ('.' * node.level) + (node.module or '')
1155+
11451156
for alias in node.names:
11461157
name = alias.asname or alias.name
11471158
if node.module == '__future__':
@@ -1153,15 +1164,15 @@ def IMPORTFROM(self, node):
11531164
# Only Python 2, local import * is a SyntaxWarning
11541165
if not PY2 and not isinstance(self.scope, ModuleScope):
11551166
self.report(messages.ImportStarNotPermitted,
1156-
node, node.module)
1167+
node, module)
11571168
continue
11581169

11591170
self.scope.importStarred = True
1160-
self.report(messages.ImportStarUsed, node, node.module)
1161-
importation = StarImportation(node.module, node)
1171+
self.report(messages.ImportStarUsed, node, module)
1172+
importation = StarImportation(module, node)
11621173
else:
11631174
importation = ImportationFrom(name, node,
1164-
node.module, alias.name)
1175+
module, alias.name)
11651176
self.addBinding(node, importation)
11661177

11671178
def TRY(self, node):

pyflakes/test/test_imports.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ def test_import_submodule_as_source_name(self):
4040
assert binding.source_statement == 'import a.b as a'
4141
assert str(binding) == 'a.b as a'
4242

43+
def test_importfrom_relative(self):
44+
binding = ImportationFrom('a', None, '.', 'a')
45+
assert binding.source_statement == 'from . import a'
46+
assert str(binding) == '.a'
47+
48+
def test_importfrom_relative_parent(self):
49+
binding = ImportationFrom('a', None, '..', 'a')
50+
assert binding.source_statement == 'from .. import a'
51+
assert str(binding) == '..a'
52+
53+
def test_importfrom_relative_with_module(self):
54+
binding = ImportationFrom('b', None, '..a', 'b')
55+
assert binding.source_statement == 'from ..a import b'
56+
assert str(binding) == '..a.b'
57+
58+
def test_importfrom_relative_with_module_as(self):
59+
binding = ImportationFrom('c', None, '..a', 'b')
60+
assert binding.source_statement == 'from ..a import b as c'
61+
assert str(binding) == '..a.b as c'
62+
4363
def test_importfrom_member(self):
4464
binding = ImportationFrom('b', None, 'a', 'b')
4565
assert binding.source_statement == 'from a import b'
@@ -65,6 +85,11 @@ def test_importfrom_star(self):
6585
assert binding.source_statement == 'from a.b import *'
6686
assert str(binding) == 'a.b.*'
6787

88+
def test_importfrom_star_relative(self):
89+
binding = StarImportation('.b', None)
90+
assert binding.source_statement == 'from .b import *'
91+
assert str(binding) == '.b.*'
92+
6893
def test_importfrom_future(self):
6994
binding = FutureImportation('print_function', None, None)
7095
assert binding.source_statement == 'from __future__ import print_function'
@@ -77,6 +102,29 @@ def test_unusedImport(self):
77102
self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
78103
self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
79104

105+
def test_unusedImport_relative(self):
106+
self.flakes('from . import fu', m.UnusedImport)
107+
self.flakes('from . import fu as baz', m.UnusedImport)
108+
self.flakes('from .. import fu', m.UnusedImport)
109+
self.flakes('from ... import fu', m.UnusedImport)
110+
self.flakes('from .. import fu as baz', m.UnusedImport)
111+
self.flakes('from .bar import fu', m.UnusedImport)
112+
self.flakes('from ..bar import fu', m.UnusedImport)
113+
self.flakes('from ...bar import fu', m.UnusedImport)
114+
self.flakes('from ...bar import fu as baz', m.UnusedImport)
115+
116+
checker = self.flakes('from . import fu', m.UnusedImport)
117+
118+
error = checker.messages[0]
119+
assert error.message == '%r imported but unused'
120+
assert error.message_args == ('.fu', )
121+
122+
checker = self.flakes('from . import fu as baz', m.UnusedImport)
123+
124+
error = checker.messages[0]
125+
assert error.message == '%r imported but unused'
126+
assert error.message_args == ('.fu as baz', )
127+
80128
def test_aliasedImport(self):
81129
self.flakes('import fu as FU, bar as FU',
82130
m.RedefinedWhileUnused, m.UnusedImport)
@@ -94,6 +142,12 @@ def test_usedImport(self):
94142
self.flakes('from baz import fu; print(fu)')
95143
self.flakes('import fu; del fu')
96144

145+
def test_usedImport_relative(self):
146+
self.flakes('from . import fu; assert fu')
147+
self.flakes('from .bar import fu; assert fu')
148+
self.flakes('from .. import fu; assert fu')
149+
self.flakes('from ..bar import fu as baz; assert baz')
150+
97151
def test_redefinedWhileUnused(self):
98152
self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
99153
self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
@@ -687,6 +741,49 @@ def test_importStar(self):
687741
pass
688742
''', m.ImportStarUsed, m.UnusedImport)
689743

744+
checker = self.flakes('from fu import *',
745+
m.ImportStarUsed, m.UnusedImport)
746+
747+
error = checker.messages[0]
748+
assert error.message.startswith("'from %s import *' used; unable ")
749+
assert error.message_args == ('fu', )
750+
751+
error = checker.messages[1]
752+
assert error.message == '%r imported but unused'
753+
assert error.message_args == ('fu.*', )
754+
755+
def test_importStar_relative(self):
756+
"""Use of import * from a relative import is reported."""
757+
self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport)
758+
self.flakes('''
759+
try:
760+
from .fu import *
761+
except:
762+
pass
763+
''', m.ImportStarUsed, m.UnusedImport)
764+
765+
checker = self.flakes('from .fu import *',
766+
m.ImportStarUsed, m.UnusedImport)
767+
768+
error = checker.messages[0]
769+
assert error.message.startswith("'from %s import *' used; unable ")
770+
assert error.message_args == ('.fu', )
771+
772+
error = checker.messages[1]
773+
assert error.message == '%r imported but unused'
774+
assert error.message_args == ('.fu.*', )
775+
776+
checker = self.flakes('from .. import *',
777+
m.ImportStarUsed, m.UnusedImport)
778+
779+
error = checker.messages[0]
780+
assert error.message.startswith("'from %s import *' used; unable ")
781+
assert error.message_args == ('..', )
782+
783+
error = checker.messages[1]
784+
assert error.message == '%r imported but unused'
785+
assert error.message_args == ('from .. import *', )
786+
690787
@skipIf(version_info < (3,),
691788
'import * below module level is a warning on Python 2')
692789
def test_localImportStar(self):
@@ -700,6 +797,14 @@ class a:
700797
from fu import *
701798
''', m.ImportStarNotPermitted)
702799

800+
checker = self.flakes('''
801+
class a:
802+
from .. import *
803+
''', m.ImportStarNotPermitted)
804+
error = checker.messages[0]
805+
assert error.message == "'from %s import *' only allowed at module level"
806+
assert error.message_args == ('..', )
807+
703808
@skipIf(version_info > (3,),
704809
'import * below module level is an error on Python 3')
705810
def test_importStarNested(self):

0 commit comments

Comments
 (0)