Skip to content

Commit 9c88c0e

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 6734c37 commit 9c88c0e

File tree

2 files changed

+32
-12
lines changed

2 files changed

+32
-12
lines changed

pyflakes/checker.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ def __init__(self, name, source):
158158
self.fullName = name
159159

160160

161+
class FutureImportation(Importation):
162+
"""
163+
A binding created by a from `__future__` import statement.
164+
165+
`__future__` imports are implicitly used.
166+
"""
167+
168+
def __init__(self, name, source, scope):
169+
super(FutureImportation, self).__init__(name, source)
170+
self.used = (scope, source)
171+
172+
161173
class Argument(Binding):
162174
"""
163175
Represents binding a name as an argument.
@@ -255,6 +267,7 @@ class GeneratorScope(Scope):
255267

256268
class ModuleScope(Scope):
257269
"""Scope for a module."""
270+
_futures_allowed = True
258271

259272

260273
class DoctestScope(ModuleScope):
@@ -310,7 +323,6 @@ def __init__(self, tree, filename='(none)', builtins=None,
310323
self.withDoctest = withDoctest
311324
self.scopeStack = [ModuleScope()]
312325
self.exceptHandlers = [()]
313-
self.futuresAllowed = True
314326
self.root = tree
315327
self.handleChildren(tree)
316328
self.runDeferred(self._deferredFunctions)
@@ -356,6 +368,20 @@ def _in_doctest(self):
356368
return (len(self.scopeStack) >= 2 and
357369
isinstance(self.scopeStack[1], DoctestScope))
358370

371+
@property
372+
def futuresAllowed(self):
373+
if not all(isinstance(scope, ModuleScope)
374+
for scope in self.scopeStack):
375+
return False
376+
377+
return self.scope._futures_allowed
378+
379+
@futuresAllowed.setter
380+
def futuresAllowed(self, value):
381+
assert value is False
382+
if isinstance(self.scope, ModuleScope):
383+
self.scope._futures_allowed = False
384+
359385
@property
360386
def scope(self):
361387
return self.scopeStack[-1]
@@ -1025,7 +1051,9 @@ def IMPORTFROM(self, node):
10251051

10261052
for alias in node.names:
10271053
name = alias.asname or alias.name
1028-
if alias.name == '*':
1054+
if node.module == '__future__':
1055+
importation = FutureImportation(name, node, self.scope)
1056+
elif alias.name == '*':
10291057
# Only Python 2, local import * is a SyntaxWarning
10301058
if not PY2 and not isinstance(self.scope, ModuleScope):
10311059
self.report(messages.ImportStarNotPermitted,
@@ -1037,8 +1065,6 @@ def IMPORTFROM(self, node):
10371065
importation = StarImportation(node.module, node)
10381066
else:
10391067
importation = Importation(name, node)
1040-
if node.module == '__future__':
1041-
importation.used = (self.scope, node)
10421068
self.addBinding(node, importation)
10431069

10441070
def TRY(self, node):

pyflakes/test/test_doctests.py

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

432432

433433
class TestOther(_DoctestMixin, TestOther):
434-
pass
434+
"""Run TestOther with each test wrapped in a doctest."""
435435

436436

437437
class TestImports(_DoctestMixin, TestImports):
438-
439-
def test_futureImport(self):
440-
"""XXX This test can't work in a doctest"""
441-
442-
def test_futureImportUsed(self):
443-
"""XXX This test can't work in a doctest"""
438+
"""Run TestImports with each test wrapped in a doctest."""
444439

445440

446441
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
447442
"""Run TestUndefinedNames with each test wrapped in a doctest."""
448-
pass

0 commit comments

Comments
 (0)