Repository: PyCQA/pyflakes (python)

Issue:
Internal error on `__all__, = ...`
Steps to reproduce:
1. Install `pyflakes==2.4.0`
2. Create a file with the name of `foo.py`
3. Place the following in the file (the comma after `__all__` is intentional):
```py
__all__, = (
    "fizz",
    "buzz",
)
```
4. Run `pyflakes foo.py`

Outcome:
```
Traceback (most recent call last):
  File "/tmp/foo/venv/bin/pyflakes", line 8, in <module>
    sys.exit(main())
...
  File "/tmp/foo/venv/lib/python3.9/site-packages/pyflakes/checker.py", line 591, in __init__
    if isinstance(source.value, (ast.List, ast.Tuple)):
AttributeError: 'Tuple' object has no attribute 'value'
```

After a bit of investigation, [this method](https://github.com/PyCQA/pyflakes/blob/master/pyflakes/checker.py#L580) assumes that `source` is a `ast.Name`. But in reality it can be `ast.Tuple` or some other more complicated target.

I think the right behaviour is to mark this as invalid to the user.

I'd be happy to work on this issue myself.

Pull request description:
ignore `__all__` when not directly assigned
resolves #674

Patch:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 45c7a4a..45f2a42 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -1291,7 +1291,14 @@ class Checker(object):
                 parent_stmt != node._pyflakes_parent and
                 not self.isLiteralTupleUnpacking(parent_stmt)):
             binding = Binding(name, node)
-        elif name == '__all__' and isinstance(self.scope, ModuleScope):
+        elif (
+                name == '__all__' and
+                isinstance(self.scope, ModuleScope) and
+                isinstance(
+                    node._pyflakes_parent,
+                    (ast.Assign, ast.AugAssign, ast.AnnAssign)
+                )
+        ):
             binding = ExportBinding(name, node._pyflakes_parent, self.scope)
         elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param):
             binding = Argument(name, self.getScopeNode(node))


Test patch:
diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py
index d5be269..07504d9 100644
--- a/pyflakes/test/test_imports.py
+++ b/pyflakes/test/test_imports.py
@@ -1057,6 +1057,12 @@ class TestSpecialAll(TestCase):
             __all__ = ["bar"]
         ''', m.UnusedImport)
 
+    def test_ignored_when_not_directly_assigned(self):
+        self.flakes('''
+        import bar
+        (__all__,) = ("foo",)
+        ''', m.UnusedImport)
+
     def test_warningSuppressed(self):
         """
         If a name is imported and unused but is named in C{__all__}, no warning