diff --git a/bugbear.py b/bugbear.py index b891b3f..be299a4 100644 --- a/bugbear.py +++ b/bugbear.py @@ -612,7 +612,7 @@ def check_for_b023(self, loop_node): if reassigned_in_loop.issuperset(err.vars): self.errors.append(err) - def check_for_b024_and_b027(self, node: ast.ClassDef): + def check_for_b024_and_b027(self, node: ast.ClassDef): # noqa: C901 """Check for inheritance from abstract classes in abc and lack of any methods decorated with abstract*""" @@ -661,6 +661,12 @@ def is_str_or_ellipsis(node): has_abstract_method = False for stmt in node.body: + # https://github.com/PyCQA/flake8-bugbear/issues/293 + # Ignore abc's that declares a class attribute that must be set + if isinstance(stmt, (ast.AnnAssign, ast.Assign)): + has_abstract_method = True + continue + # only check function defs if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): continue diff --git a/tests/b024.py b/tests/b024.py index 7a11870..256b9ef 100644 --- a/tests/b024.py +++ b/tests/b024.py @@ -110,3 +110,20 @@ def method(self): class keyword_abc_2(metaclass=abc.ABC): # safe def method(self): foo() + + +class abc_set_class_variable_1(ABC): # safe + foo: int + + +class abc_set_class_variable_2(ABC): # safe + foo = 2 + + +class abc_set_class_variable_3(ABC): # safe + foo: int = 2 + + +# this doesn't actually declare a class variable, it's just an expression +class abc_set_class_variable_4(ABC): # error + foo diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py index 15edd32..69719ab 100644 --- a/tests/test_bugbear.py +++ b/tests/test_bugbear.py @@ -364,6 +364,7 @@ def test_b024(self): B024(58, 0, vars=("MetaBase_1",)), B024(69, 0, vars=("abc_Base_1",)), B024(74, 0, vars=("abc_Base_2",)), + B024(128, 0, vars=("abc_set_class_variable_4",)), ) self.assertEqual(errors, expected)