Repository: wemake-services/wemake-python-styleguide

Issue:
Discrepancy between isort and flake8 line length settings
I have a question about the configuration for [nitpick](https://pypi.org/project/nitpick/). Why is the line length different for flake8 and isort?

```toml
["setup.cfg".flake8]
max-line-length = 80

["setup.cfg".isort]
line_length = 79
```

`isort --help`
>   -w LINE_LENGTH, --line-width LINE_LENGTH
                        The max length of an import line (used for wrapping
                        long imports).

`flake8 --help`
>   --max-line-length=n   Maximum allowed line length for the entirety of this
                        run. (Default: 79)

If you're changing the default for flake from 79 to 80, why not do the same for isort?


Discussion:

Resolving patch:
diff --git a/wemake_python_styleguide/constants.py b/wemake_python_styleguide/constants.py
--- a/wemake_python_styleguide/constants.py
+++ b/wemake_python_styleguide/constants.py
@@ -254,7 +254,7 @@
 #: List of magic methods that are not allowed to be generators.
 YIELD_MAGIC_METHODS_BLACKLIST: Final = ALL_MAGIC_METHODS.difference({
     # Allowed to be used with ``yield`` keyword:
-    '__call__',  # Fixes Issue:146
+    '__call__',
     '__iter__',
 })
 
diff --git a/wemake_python_styleguide/violations/best_practices.py b/wemake_python_styleguide/violations/best_practices.py
--- a/wemake_python_styleguide/violations/best_practices.py
+++ b/wemake_python_styleguide/violations/best_practices.py
@@ -76,6 +76,7 @@
    InfiniteWhileLoopViolation
    ImportCollisionViolation
    FloatComplexCompareViolation
+   SingleElementDestructuringViolation
 
 Best practices
 --------------
@@ -140,6 +141,7 @@
 .. autoclass:: InfiniteWhileLoopViolation
 .. autoclass:: ImportCollisionViolation
 .. autoclass:: FloatComplexCompareViolation
+.. autoclass:: SingleElementDestructuringViolation
 
 """
 
@@ -2331,3 +2333,30 @@ class FloatComplexCompareViolation(ASTViolation):
 
     error_template = 'Found comparison with float or complex number'
     code = 459
+
+
+@final
+class SingleElementDestructuringViolation(ASTViolation):
+    """
+    Forbids to have single element destructuring.
+
+    Reasoning:
+        Having single element destructuring is not readable.
+
+    Solution:
+        Use access by index instead.
+
+    Example::
+
+        # Correct:
+        first = single_element_list[0]
+
+        # Wrong:
+        (first,) = [1]
+
+    .. versionadded:: 0.15.0
+
+    """
+
+    error_template = 'Found single element destructuring'
+    code = 460
diff --git a/wemake_python_styleguide/visitors/ast/builtins.py b/wemake_python_styleguide/visitors/ast/builtins.py
--- a/wemake_python_styleguide/visitors/ast/builtins.py
+++ b/wemake_python_styleguide/visitors/ast/builtins.py
@@ -399,6 +399,7 @@ def visit_Assign(self, node: ast.Assign) -> None:
             UnpackingIterableToListViolation
             MultipleAssignmentsViolation
             WrongUnpackingViolation
+            SingleElementDestructuringViolation
 
         """
         self._check_assign_targets(node)
@@ -406,7 +407,7 @@ def visit_Assign(self, node: ast.Assign) -> None:
         for target in node.targets:
             self._check_unpacking_target_types(target)
 
-        if isinstance(node.targets[0], ast.Tuple):
+        if isinstance(node.targets[0], (ast.Tuple, ast.List)):
             self._check_unpacking_targets(node, node.targets[0].elts)
         self.generic_visit(node)
 
@@ -421,6 +422,13 @@ def _check_unpacking_targets(
         node: ast.AST,
         targets: Iterable[ast.AST],
     ) -> None:
+        targets = tuple(targets)
+
+        if len(targets) == 1:
+            self.add_violation(
+                best_practices.SingleElementDestructuringViolation(node),
+            )
+
         for target in targets:
             target_name = extract_name(target)
             if target_name is None:  # it means, that non name node was used


Test patch:
diff --git a/tests/fixtures/noqa/noqa.py b/tests/fixtures/noqa/noqa.py
--- a/tests/fixtures/noqa/noqa.py
+++ b/tests/fixtures/noqa/noqa.py
@@ -712,7 +712,7 @@ def consecutive_yields():
     'wrong',
 ]
 
-*numbers, = [4, 7]  # noqa: WPS356
+*numbers, = [4, 7]  # noqa: WPS356, WPS460
 [first_number, second_number] = [4, 7]  # noqa: WPS359
 
 for element in range(10):
diff --git a/tests/test_checker/test_noqa.py b/tests/test_checker/test_noqa.py
--- a/tests/test_checker/test_noqa.py
+++ b/tests/test_checker/test_noqa.py
@@ -232,6 +232,7 @@
     'WPS457': 1,
     'WPS458': 1,
     'WPS459': 1,
+    'WPS460': 1,
 
     'WPS500': 1,
     'WPS501': 1,
diff --git a/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py b/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py
--- a/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py
+++ b/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py
@@ -1,14 +1,19 @@
 import pytest
 
 from wemake_python_styleguide.violations.best_practices import (
+    SingleElementDestructuringViolation,
     WrongUnpackingViolation,
 )
+from wemake_python_styleguide.violations.consistency import (
+    UnpackingIterableToListViolation,
+)
 from wemake_python_styleguide.visitors.ast.builtins import (
     WrongAssignmentVisitor,
 )
 
 single_assignment = '{0} = 1'
-
+sequence_assignment1 = 'first, {0} = {1}'
+sequence_assignment2 = '{0}, second = {1}'
 tuple_assignment1 = 'first, {0} = (1, 2)'
 tuple_assignment2 = '{0}, second = (1, 2)'
 
@@ -71,9 +76,16 @@ def wrapper():
         ...
 """
 
+correct_single_destructuring = 'first = {0}'
+wrong_single_destructuring1 = 'first, = {0}'
+wrong_single_destructuring2 = '(first,) = {0}'
+wrong_single_destructuring3 = '[first] = {0}'
+
 
 @pytest.mark.parametrize('code', [
     single_assignment,
+    sequence_assignment1,
+    sequence_assignment2,
     tuple_assignment1,
     tuple_assignment2,
     spread_assignment1,
@@ -89,15 +101,20 @@ def wrapper():
     with_unpacking1,
     with_unpacking2,
 ])
+@pytest.mark.parametrize('target', [
+    '(1, 2)',
+    '[1, 2]',
+])
 def test_correct_unpacking(
     assert_errors,
     parse_ast_tree,
     code,
+    target,
     default_options,
     mode,
 ):
     """Testing that correct assignments work."""
-    tree = parse_ast_tree(mode(code.format('some_name')))
+    tree = parse_ast_tree(mode(code.format('some_name', target)))
 
     visitor = WrongAssignmentVisitor(default_options, tree=tree)
     visitor.run()
@@ -127,6 +144,8 @@ def test_correct_assignment(
 
 
 @pytest.mark.parametrize('code', [
+    sequence_assignment1,
+    sequence_assignment2,
     tuple_assignment1,
     tuple_assignment2,
     spread_assignment1,
@@ -148,18 +167,75 @@ def test_correct_assignment(
     'some[0]["key"]',
     'some["key"][0]',
 ])
+@pytest.mark.parametrize('target', [
+    '(1, 2)',
+    '[1, 2]',
+])
 def test_multiple_assignments(
     assert_errors,
     parse_ast_tree,
     code,
+    target,
     assignment,
     default_options,
     mode,
 ):
     """Testing that multiple assignments are restricted."""
-    tree = parse_ast_tree(mode(code.format(assignment)))
+    tree = parse_ast_tree(mode(code.format(assignment, target)))
 
     visitor = WrongAssignmentVisitor(default_options, tree=tree)
     visitor.run()
 
     assert_errors(visitor, [WrongUnpackingViolation])
+
+
+@pytest.mark.parametrize('target', [
+    '(1,)[0]',
+    '[1][0]',
+])
+def test_correct_destructing(
+    assert_errors,
+    parse_ast_tree,
+    target,
+    default_options,
+    mode,
+):
+    """Testing that correct elements destructuring work."""
+    tree = parse_ast_tree(mode(correct_single_destructuring.format(target)))
+
+    visitor = WrongAssignmentVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('code', [
+    wrong_single_destructuring1,
+    wrong_single_destructuring2,
+    wrong_single_destructuring3,
+])
+@pytest.mark.parametrize('assignment', [
+    '(1,)',
+    '[1]',
+    'some',
+    'some()',
+])
+def test_single_element_destructing(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    assignment,
+    default_options,
+    mode,
+):
+    """Testing that single element destructuring is restricted."""
+    tree = parse_ast_tree(mode(code.format(assignment)))
+
+    visitor = WrongAssignmentVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(
+        visitor,
+        [SingleElementDestructuringViolation],
+        ignored_types=UnpackingIterableToListViolation,
+    )