From b8b9abd5ec5990af88c2315d7d6a1afaaac6b699 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 May 2024 18:34:02 +0000 Subject: [PATCH] Recognize new-style attrs decorators in too-few-public-methods check (#9346) (#9596) 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. (cherry picked from commit c032181ef49bfd14cecce9cdf9cec293379f1637) Co-authored-by: akirchhoff-modular --- doc/whatsnew/fragments/9345.false_positive | 4 +++ pylint/checkers/design_analysis.py | 6 +++++ .../t/too/too_few_public_methods_37.py | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 doc/whatsnew/fragments/9345.false_positive 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 8ff26eca15..de9cac645d 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