Skip to content

Commit bfab363

Browse files
committed
Allow __future__ in doctest
Replaces plain attribute Checker.futuresAllowed with a property that supports __future__ in both module and doctest.
1 parent e4c447b commit bfab363

File tree

2 files changed

+43
-14
lines changed

2 files changed

+43
-14
lines changed

pyflakes/checker.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ def redefines(self, other):
147147
return isinstance(other, Definition) and self.name == other.name
148148

149149

150+
class FutureImportation(Importation):
151+
"""
152+
A binding created by a from `__future__` import statement.
153+
154+
`__future__` imports are implicitly used.
155+
"""
156+
157+
def __init__(self, name, source, scope):
158+
super(FutureImportation, self).__init__(name, source)
159+
self.used = (scope, source)
160+
161+
150162
class Argument(Binding):
151163
"""
152164
Represents binding a name as an argument.
@@ -244,6 +256,7 @@ class GeneratorScope(Scope):
244256

245257
class ModuleScope(Scope):
246258
"""Scope for a module."""
259+
_futures_allowed = True
247260

248261

249262
class DoctestScope(ModuleScope):
@@ -299,7 +312,6 @@ def __init__(self, tree, filename='(none)', builtins=None,
299312
self.withDoctest = withDoctest
300313
self.scopeStack = [ModuleScope()]
301314
self.exceptHandlers = [()]
302-
self.futuresAllowed = True
303315
self.root = tree
304316
self.handleChildren(tree)
305317
self.runDeferred(self._deferredFunctions)
@@ -314,6 +326,28 @@ def __init__(self, tree, filename='(none)', builtins=None,
314326
self.popScope()
315327
self.checkDeadScopes()
316328

329+
@property
330+
def futuresAllowed(self):
331+
if len(self.scopeStack) > 2:
332+
return False
333+
334+
if not all(isinstance(scope, ModuleScope)
335+
for scope in self.scopeStack):
336+
return False
337+
338+
if not self.scope._futures_allowed:
339+
return False
340+
341+
return all(isinstance(binding, FutureImportation)
342+
for binding in self.scope.values())
343+
344+
@futuresAllowed.setter
345+
def futuresAllowed(self, value):
346+
assert value == False
347+
if not isinstance(self.scope, ModuleScope):
348+
return
349+
self.scope._futures_allowed = False
350+
317351
def deferFunction(self, callable):
318352
"""
319353
Schedule a function handler to be called just before completion.
@@ -975,7 +1009,10 @@ def IMPORTFROM(self, node):
9751009
self.futuresAllowed = False
9761010

9771011
for alias in node.names:
978-
if alias.name == '*':
1012+
name = alias.asname or alias.name
1013+
if node.module == '__future__':
1014+
importation = FutureImportation(name, node, self.scope)
1015+
elif alias.name == '*':
9791016
# Only Python 2, local import * is a SyntaxWarning
9801017
if not PY2 and not isinstance(self.scope, ModuleScope):
9811018
self.report(messages.ImportStarNotPermitted,
@@ -984,10 +1021,8 @@ def IMPORTFROM(self, node):
9841021
self.scope.importStarred = True
9851022
self.report(messages.ImportStarUsed, node, node.module)
9861023
continue
987-
name = alias.asname or alias.name
988-
importation = Importation(name, node)
989-
if node.module == '__future__':
990-
importation.used = (self.scope, node)
1024+
else:
1025+
importation = Importation(name, node)
9911026
self.addBinding(node, importation)
9921027

9931028
def TRY(self, node):

pyflakes/test/test_doctests.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -383,18 +383,12 @@ def func():
383383

384384

385385
class TestOther(_DoctestMixin, TestOther):
386-
pass
386+
"""Run TestOther with each test wrapped in a doctest."""
387387

388388

389389
class TestImports(_DoctestMixin, TestImports):
390-
391-
def test_futureImport(self):
392-
"""XXX This test can't work in a doctest"""
393-
394-
def test_futureImportUsed(self):
395-
"""XXX This test can't work in a doctest"""
390+
"""Run TestImports with each test wrapped in a doctest."""
396391

397392

398393
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
399394
"""Run TestUndefinedNames with each test wrapped in a doctest."""
400-
pass

0 commit comments

Comments
 (0)