Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid extra `object` in parent classes list
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

Today I saw this:

```python
class Some(FirstMixin, SecondMixin, object): ...
```

Which result in the following `mro`:

```python
>>> Some.__mro__
(<class '__main__.Some'>, <class '__main__.FirstMixin'>, <class '__main__.SecondMixin'>, <class 'object'>)
```

## Reasoning

We should allow `object` only when we explicitly use it as a single parent class.
When there is an other class or there are multiple parents - we should not allow it for the consistency reasons.



Discussion:
@sobolevn , started working on this!

Resolving patch:
diff --git a/wemake_python_styleguide/violations/consistency.py b/wemake_python_styleguide/violations/consistency.py
--- a/wemake_python_styleguide/violations/consistency.py
+++ b/wemake_python_styleguide/violations/consistency.py
@@ -46,6 +46,7 @@
    RedundantComparisonViolation
    WrongConditionalViolation
    MissingSpaceBetweenKeywordAndParenViolation
+   WrongParentClassListDef
 
 Consistency checks
 ------------------
@@ -65,6 +66,7 @@
 .. autoclass:: RedundantComparisonViolation
 .. autoclass:: WrongConditionalViolation
 .. autoclass:: MissingSpaceBetweenKeywordAndParenViolation
+.. autoclass:: WrongParentClassListDef
 """
 
 from wemake_python_styleguide.types import final
@@ -578,3 +580,35 @@ class WrongConditionalViolation(ASTViolation):
     #: Error message shown to the user.
     error_template = 'Conditional always evaluates to same result'
     code = 314
+
+
+class WrongParentClassListDef(ASTViolation):
+    """
+    Forbid extra `object` in parent classes list.
+
+    Reasoning:
+        We should allow object only when
+        we explicitly use it as a single parent class.
+        When there is an other class or there are multiple
+        parents - we should not allow it for the consistency reasons.
+
+    Solution:
+        Remove extra 'object'.
+
+    Example::
+
+       # Correct:
+       class SomeClassName(object): ...
+       class SomeClassName(FirstParentClass, SecondParenClass): ...
+
+       # Wrong:
+       class SomeClassName(FirstParentClass, SecondParentClass, object): ...
+
+    Note:
+        Returns Z315 as error code
+    """
+
+    should_use_text = False
+    #: Error message shown to the user
+    error_template = 'Founded extra `object` in parent classes list'
+    code = 315
diff --git a/wemake_python_styleguide/visitors/ast/classes.py b/wemake_python_styleguide/visitors/ast/classes.py
--- a/wemake_python_styleguide/visitors/ast/classes.py
+++ b/wemake_python_styleguide/visitors/ast/classes.py
@@ -11,6 +11,7 @@
 )
 from wemake_python_styleguide.violations.consistency import (
     RequiredBaseClassViolation,
+    WrongParentClassListDef,
 )
 from wemake_python_styleguide.visitors.base import BaseNodeVisitor
 from wemake_python_styleguide.visitors.decorators import alias
@@ -46,15 +47,26 @@ def _check_base_class(self, node: ast.ClassDef) -> None:
         if len(node.bases) == 0:
             self.add_violation(RequiredBaseClassViolation(node, text=node.name))
 
+    def _check_extra_object(self, node: ast.ClassDef) -> None:
+        """Check 'object' class in parent list."""
+        if len(node.bases) >= 2:
+            for base_name in node.bases:
+                id_attr = getattr(base_name, 'id', None)
+                if id_attr == 'object':
+                    self.add_violation(WrongParentClassListDef(node,
+                                                               text=id_attr))
+
     def visit_ClassDef(self, node: ast.ClassDef) -> None:
         """
         Checking class definitions.
 
         Raises:
             RequiredBaseClassViolation
+            WrongParentClassListDef
 
         """
         self._check_base_class(node)
+        self._check_extra_object(node)
         self.generic_visit(node)
 
     def visit_any_function(self, node: AnyFunctionDef) -> None:


Test patch:
diff --git a/tests/test_visitors/test_ast/test_classes/test_parent_class_visitor.py b/tests/test_visitors/test_ast/test_classes/test_parent_class_visitor.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_classes/test_parent_class_visitor.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.consistency import (
+    WrongParentClassListDef,
+)
+from wemake_python_styleguide.visitors.ast.classes import WrongClassVisitor
+
+extra_object = 'class TestClassName(FirstName, SecondName, object): ...'
+
+
+@pytest.mark.parametrize('code', [
+    extra_object,
+])
+def test_wrong_class_definition_multiple_parent(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+):
+    """Testing wrong class definition."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongClassVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [WrongParentClassListDef])
+
+
+single_extra_object = 'class TestClassName(object): ...'
+correct_list = 'class TestClassName(FirstTestClass, SecondTestClass): ...'
+
+
+@pytest.mark.parametrize('code', [
+    single_extra_object,
+    correct_list,
+])
+def test_correct_class_definitions(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+):
+    """Testing correct class definition with single parent."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongClassVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])