Skip to content

Commit 1d9764d

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 d34b88c commit 1d9764d

File tree

2 files changed

+35
-14
lines changed

2 files changed

+35
-14
lines changed

pyflakes/checker.py

Lines changed: 33 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)
@@ -345,6 +357,20 @@ def _in_doctest(self):
345357
return (len(self.scopeStack) >= 2 and
346358
isinstance(self.scopeStack[1], DoctestScope))
347359

360+
@property
361+
def futuresAllowed(self):
362+
if not all(isinstance(scope, ModuleScope)
363+
for scope in self.scopeStack):
364+
return False
365+
366+
return self.scope._futures_allowed
367+
368+
@futuresAllowed.setter
369+
def futuresAllowed(self, value):
370+
assert value is False
371+
if isinstance(self.scope, ModuleScope):
372+
self.scope._futures_allowed = False
373+
348374
@property
349375
def scope(self):
350376
return self.scopeStack[-1]
@@ -974,7 +1000,10 @@ def IMPORTFROM(self, node):
9741000
self.futuresAllowed = False
9751001

9761002
for alias in node.names:
977-
if alias.name == '*':
1003+
name = alias.asname or alias.name
1004+
if node.module == '__future__':
1005+
importation = FutureImportation(name, node, self.scope)
1006+
elif alias.name == '*':
9781007
# Only Python 2, local import * is a SyntaxWarning
9791008
if not PY2 and not isinstance(self.scope, ModuleScope):
9801009
self.report(messages.ImportStarNotPermitted,
@@ -983,10 +1012,8 @@ def IMPORTFROM(self, node):
9831012
self.scope.importStarred = True
9841013
self.report(messages.ImportStarUsed, node, node.module)
9851014
continue
986-
name = alias.asname or alias.name
987-
importation = Importation(name, node)
988-
if node.module == '__future__':
989-
importation.used = (self.scope, node)
1015+
else:
1016+
importation = Importation(name, node)
9901017
self.addBinding(node, importation)
9911018

9921019
def TRY(self, node):

pyflakes/test/test_doctests.py

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

409409

410410
class TestOther(_DoctestMixin, TestOther):
411-
pass
411+
"""Run TestOther with each test wrapped in a doctest."""
412412

413413

414414
class TestImports(_DoctestMixin, TestImports):
415-
416-
def test_futureImport(self):
417-
"""XXX This test can't work in a doctest"""
418-
419-
def test_futureImportUsed(self):
420-
"""XXX This test can't work in a doctest"""
415+
"""Run TestImports with each test wrapped in a doctest."""
421416

422417

423418
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
424419
"""Run TestUndefinedNames with each test wrapped in a doctest."""
425-
pass

0 commit comments

Comments
 (0)