Repository: PyCQA/pyflakes
Pull request #415 (2019-01-21 00:24:46)

Fix regression with `__all__ = [not, strings]`
Regression introduced in 47a164136982ea6ef80f302200b2a29d3a4b1c3c

Example trace before fix:

```
...

  File "/home/asottile/workspace/pyflakes/pyflakes/checker.py", line 998, in handleNodeStore
    binding = ExportBinding(name, node.parent, self.scope)
  File "/home/asottile/workspace/pyflakes/pyflakes/checker.py", line 426, in __init__
    self.names += ast.literal_eval(source.value)
  File "/usr/lib/python3.6/ast.py", line 85, in literal_eval
    return _convert(node_or_string)
  File "/usr/lib/python3.6/ast.py", line 61, in _convert
    return list(map(_convert, node.elts))
  File "/usr/lib/python3.6/ast.py", line 84, in _convert
    raise ValueError('malformed node or string: ' + repr(node))
ValueError: malformed node or string: <_ast.Attribute object at 0x7f6d2bf37b70>
```

Found via [this file](https://github.com/pytest-dev/pytest-bdd/blob/6db3496ad2cd6434b6cc2cbaadf905fba4b9c215/pytest_bdd/__init__.py#L8)

CC @CLiu13 

asottile: actually, this still has problems now that I look at the other patch -- let me try and restore the original behaviour plus the new behaviour
asottile: ok, should be good now -- no more literal_eval

Patch:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index b8d7be7..650d788 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -422,21 +422,27 @@ class ExportBinding(Binding):
             self.names = list(scope['__all__'].names)
         else:
             self.names = []
+
+        def _add_to_names(container):
+            for node in container.elts:
+                if isinstance(node, ast.Str):
+                    self.names.append(node.s)
+
         if isinstance(source.value, (ast.List, ast.Tuple)):
-            self.names += ast.literal_eval(source.value)
+            _add_to_names(source.value)
         # If concatenating lists
         elif isinstance(source.value, ast.BinOp):
             currentValue = source.value
             while isinstance(currentValue.right, ast.List):
                 left = currentValue.left
                 right = currentValue.right
-                self.names += ast.literal_eval(right)
+                _add_to_names(right)
                 # If more lists are being added
                 if isinstance(left, ast.BinOp):
                     currentValue = left
                 # If just two lists are being added
                 elif isinstance(left, ast.List):
-                    self.names += ast.literal_eval(left)
+                    _add_to_names(left)
                     # All lists accounted for - done
                     break
                 # If not list concatenation


Test patch:
diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py
index 193f9d3..13e7bef 100644
--- a/pyflakes/test/test_imports.py
+++ b/pyflakes/test/test_imports.py
@@ -1093,6 +1093,33 @@ class TestSpecialAll(TestCase):
         __all__ = ['a'] + ['b'] + ['c']
         ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport)
 
+    def test_all_with_attributes(self):
+        self.flakes('''
+        from foo import bar
+        __all__ = [bar.__name__]
+        ''')
+
+    def test_all_with_names(self):
+        # not actually valid, but shouldn't produce a crash
+        self.flakes('''
+        from foo import bar
+        __all__ = [bar]
+        ''')
+
+    def test_all_with_attributes_added(self):
+        self.flakes('''
+        from foo import bar
+        from bar import baz
+        __all__ = [bar.__name__] + [baz.__name__]
+        ''')
+
+    def test_all_mixed_attributes_and_strings(self):
+        self.flakes('''
+        from foo import bar
+        from foo import baz
+        __all__ = ['bar', baz.__name__]
+        ''')
+
     def test_unboundExported(self):
         """
         If C{__all__} includes a name which is not bound, a warning is emitted.