Repository: PyCQA/pyflakes
Pull request #398 (2018-12-31 18:44:35)

Update TravisCI to use flake8 3.6.0
Update TravisCI & `tox.ini` to use v3.6.0 in flake8 rather than v2.1.0 which is 4 years old.

Update `tox.ini` to ignore W504. This is ignored by default in flake8 but it is enabled by the `select`. *W503* and *W504* are opposites of one another and determines when to break with a statement and an operator.  PEP8 recommends breaking **after** binary operator (they originally recommended breaking before but changed).

Fix some expressions that break before binary operators.

Move import statement to top to fix another warning.

Change pep8 to pycodestyle and bump version


Patch:
diff --git a/.travis.yml b/.travis.yml
index 2113aec..5d92e4f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,4 +18,4 @@ install:
   - pip list
 script:
   - python setup.py test -q
-  - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then pip install flake8==2.1.0 pep8==1.5.6 && flake8 --version && flake8 pyflakes setup.py; fi
+  - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then pip install flake8==3.6.0 && flake8 --version && flake8 pyflakes setup.py; fi
diff --git a/pyflakes/api.py b/pyflakes/api.py
index 805a886..3ddb386 100644
--- a/pyflakes/api.py
+++ b/pyflakes/api.py
@@ -5,6 +5,7 @@ from __future__ import with_statement
 
 import sys
 import os
+import platform
 import re
 import _ast
 
@@ -91,9 +92,6 @@ def checkPath(filename, reporter=None):
     try:
         with open(filename, 'rb') as f:
             codestr = f.read()
-    except UnicodeError:
-        reporter.unexpectedError(filename, 'problem decoding source')
-        return 1
     except IOError:
         msg = sys.exc_info()[1]
         reporter.unexpectedError(filename, msg.args[1])
@@ -186,6 +184,14 @@ def _exitOnSignal(sigName, message):
         pass
 
 
+def _get_version():
+    """
+    Retrieve and format package version along with python version & OS used
+    """
+    return ('%s Python %s on %s' %
+            (__version__, platform.python_version(), platform.system()))
+
+
 def main(prog=None, args=None):
     """Entry point for the script "pyflakes"."""
     import optparse
@@ -194,7 +200,7 @@ def main(prog=None, args=None):
     _exitOnSignal('SIGINT', '... stopped')
     _exitOnSignal('SIGPIPE', 1)
 
-    parser = optparse.OptionParser(prog=prog, version=__version__)
+    parser = optparse.OptionParser(prog=prog, version=_get_version())
     (__, args) = parser.parse_args(args=args)
     reporter = modReporter._makeDefaultReporter()
     if args:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index e05bdf4..a090d8a 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -10,8 +10,11 @@ import doctest
 import os
 import sys
 
+from pyflakes import messages
+
 PY2 = sys.version_info < (3, 0)
-PY34 = sys.version_info < (3, 5)    # Python 2.7 to 3.4
+PY35_PLUS = sys.version_info >= (3, 5)    # Python 3.5 and above
+PY36_PLUS = sys.version_info >= (3, 6)    # Python 3.6 and above
 try:
     sys.pypy_version_info
     PYPY = True
@@ -20,8 +23,6 @@ except AttributeError:
 
 builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins'))
 
-from pyflakes import messages
-
 
 if PY2:
     def getNodeType(node_class):
@@ -55,12 +56,12 @@ else:
         if isinstance(n, ast.Try):
             return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
 
-if PY34:
-    FOR_TYPES = (ast.For,)
-    LOOP_TYPES = (ast.While, ast.For)
-else:
+if PY35_PLUS:
     FOR_TYPES = (ast.For, ast.AsyncFor)
     LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor)
+else:
+    FOR_TYPES = (ast.For,)
+    LOOP_TYPES = (ast.While, ast.For)
 
 
 class _FieldsOrder(dict):
@@ -211,8 +212,8 @@ class VariableKey(object):
 
     def __eq__(self, compare):
         return (
-            compare.__class__ == self.__class__
-            and compare.name == self.name
+            compare.__class__ == self.__class__ and
+            compare.name == self.name
         )
 
     def __hash__(self):
@@ -461,11 +462,11 @@ class FunctionScope(Scope):
         Return a generator for the assignments which have not been used.
         """
         for name, binding in self.items():
-            if (not binding.used
-                    and name != '_'  # see issue #202
-                    and name not in self.globals
-                    and not self.usesLocals
-                    and isinstance(binding, Assignment)):
+            if (not binding.used and
+                    name != '_' and  # see issue #202
+                    name not in self.globals and
+                    not self.usesLocals and
+                    isinstance(binding, Assignment)):
                 yield name, binding
 
 
@@ -486,6 +487,9 @@ class DoctestScope(ModuleScope):
 # Globally defined names which are not attributes of the builtins module, or
 # are only present on some platforms.
 _MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
+# module scope annotation will store in `__annotations__`, see also PEP 526.
+if PY36_PLUS:
+    _MAGIC_GLOBALS.append('__annotations__')
 
 
 def getNodeName(node):
@@ -1221,8 +1225,8 @@ class Checker(object):
         # Locate the name in locals / function / globals scopes.
         if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
             self.handleNodeLoad(node)
-            if (node.id == 'locals' and isinstance(self.scope, FunctionScope)
-                    and isinstance(node.parent, ast.Call)):
+            if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and
+                    isinstance(node.parent, ast.Call)):
                 # we are doing locals() call in current scope
                 self.scope.usesLocals = True
         elif isinstance(node.ctx, (ast.Store, ast.AugStore, ast.Param)):
diff --git a/tox.ini b/tox.ini
index f3db2bb..3a07d70 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,14 +4,13 @@ envlist =
     py27,py34,py35,py36,py37,pypy,pypy3
 
 [testenv]
-deps =
+deps = flake8==3.6.0
 commands =
-    pip install flake8==2.1.0 pep8==1.5.6
-    flake8 --version
     python setup.py test -q
     flake8 pyflakes setup.py
 
 [flake8]
 select = E,F,W
+ignore = W504
 builtins = unicode
 max_line_length = 89


Test patch:
diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py
index 88eba93..222ffce 100644
--- a/pyflakes/test/test_undefined_names.py
+++ b/pyflakes/test/test_undefined_names.py
@@ -227,6 +227,14 @@ class Test(TestCase):
         """
         self.flakes('WindowsError')
 
+    @skipIf(version_info < (3, 6), 'new feature in 3.6')
+    def test_moduleAnnotations(self):
+        """
+        Use of the C{__annotations__} in module scope should not emit
+        an undefined name warning when version is greater than or equal to 3.6.
+        """
+        self.flakes('__annotations__')
+
     def test_magicGlobalsFile(self):
         """
         Use of the C{__file__} magic global should not emit an undefined name