Recognize new-style attrs decorators in too-few-public-methods check (#9346)
Beginning with attrs 21.1.0, the recommended way to use attrs is through
`import attrs` and using `attrs.define`/`attrs.frozen`, not `import
attr` and `attr.s` or `attr.attrs`. Pylint does understand `attr.attrs`
(#2988), but new-style uses of attrs are not understood to be data class
decorators.
Modify `_is_exempt_from_public_methods` to recognize `attrs.define` and
`attrs.frozen` in a similar way as is currently done with
`dataclasses.dataclass`.
Closes #9345.
diff --git a/doc/whatsnew/fragments/9345.false_positive b/doc/whatsnew/fragments/9345.false_positive
new file mode 100644
index 0000000..af8a386
--- /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 8ff26ec..de9cac6 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 @@
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 db9c9f1..3d3a125 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 @@
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