diff --git a/doc/whatsnew/fragments/9345.false_positive b/doc/whatsnew/fragments/9345.false_positive new file mode 100644 index 0000000000..af8a3866b3 --- /dev/null +++ b/doc/whatsnew/fragments/9345.false_positive @@ -0,0 +1,4 @@ +Treat `attrs.define` and `attrs.frozen` as dataclass decorators in +`too-few-public-methods` check. + +Closes #9345 diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py index 97623feae8..8dfe6feca3 100644 --- a/pylint/checkers/design_analysis.py +++ b/pylint/checkers/design_analysis.py @@ -92,6 +92,8 @@ SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") DATACLASSES_DECORATORS = frozenset({"dataclass", "attrs"}) DATACLASS_IMPORT = "dataclasses" +ATTRS_DECORATORS = frozenset({"define", "frozen"}) +ATTRS_IMPORT = "attrs" TYPING_NAMEDTUPLE = "typing.NamedTuple" TYPING_TYPEDDICT = "typing.TypedDict" TYPING_EXTENSIONS_TYPEDDICT = "typing_extensions.TypedDict" @@ -214,6 +216,10 @@ def _is_exempt_from_public_methods(node: astroid.ClassDef) -> bool: or DATACLASS_IMPORT in root_locals ): return True + if name in ATTRS_DECORATORS and ( + root_locals.intersection(ATTRS_DECORATORS) or ATTRS_IMPORT in root_locals + ): + return True return False diff --git a/tests/functional/t/too/too_few_public_methods_37.py b/tests/functional/t/too/too_few_public_methods_37.py index db9c9f171e..3d3a12517b 100644 --- a/tests/functional/t/too/too_few_public_methods_37.py +++ b/tests/functional/t/too/too_few_public_methods_37.py @@ -8,6 +8,9 @@ import typing from dataclasses import dataclass +import attrs # pylint: disable=import-error +from attrs import define, frozen # pylint: disable=import-error + @dataclasses.dataclass class ScheduledTxSearchModel: @@ -40,3 +43,27 @@ class Point: def to_array(self): """Convert to a NumPy array `np.array((x, y, z))`.""" return self.attr1 + + +@define +class AttrsBarePoint: + x: float + y: float + + +@frozen +class AttrsBareFrozenPoint: + x: float + y: float + + +@attrs.define +class AttrsQualifiedPoint: + x: float + y: float + + +@attrs.frozen +class AttrsQualifiedFrozenPoint: + x: float + y: float