Skip to content

Commit 319d745

Browse files
authored
Handle attrs' __attrs_init__ method (#13865)
If an `attrs` class does not generate the `__init__` method for whatever reason, the method is still actually generated, under the name `__attrs_init__`. This is intended to allow a user `__init__` to then delegate to the `attrs` implementation to do the assignment (especially useful with frozen classes). This PR makes Mypy typecheck this method in this situation.
1 parent 68ab69c commit 319d745

File tree

2 files changed

+25
-4
lines changed

2 files changed

+25
-4
lines changed

mypy/plugins/attrs.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,8 @@ def attr_class_maker_callback(
324324
}
325325

326326
adder = MethodAdder(ctx)
327-
if init:
328-
_add_init(ctx, attributes, adder)
327+
# If __init__ is not being generated, attrs still generates it as __attrs_init__ instead.
328+
_add_init(ctx, attributes, adder, "__init__" if init else "__attrs_init__")
329329
if order:
330330
_add_order(ctx, adder)
331331
if frozen:
@@ -749,7 +749,10 @@ def _make_frozen(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute])
749749

750750

751751
def _add_init(
752-
ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute], adder: MethodAdder
752+
ctx: mypy.plugin.ClassDefContext,
753+
attributes: list[Attribute],
754+
adder: MethodAdder,
755+
method_name: str,
753756
) -> None:
754757
"""Generate an __init__ method for the attributes and add it to the class."""
755758
# Convert attributes to arguments with kw_only arguments at the end of
@@ -777,7 +780,7 @@ def _add_init(
777780
for a in args:
778781
a.variable.type = AnyType(TypeOfAny.implementation_artifact)
779782
a.type_annotation = AnyType(TypeOfAny.implementation_artifact)
780-
adder.add_method("__init__", args, NoneType())
783+
adder.add_method(method_name, args, NoneType())
781784

782785

783786
def _add_attrs_magic_attribute(

test-data/unit/check-attr.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,24 @@ takes_attrs_cls(A(1, "")) # E: Argument 1 to "takes_attrs_cls" has incompatible
14981498
takes_attrs_instance(A) # E: Argument 1 to "takes_attrs_instance" has incompatible type "Type[A]"; expected "AttrsInstance" # N: ClassVar protocol member AttrsInstance.__attrs_attrs__ can never be matched by a class object
14991499
[builtins fixtures/attr.pyi]
15001500

1501+
[case testAttrsInitMethodAlwaysGenerates]
1502+
from typing import Tuple
1503+
import attr
1504+
1505+
@attr.define(init=False)
1506+
class A:
1507+
b: int
1508+
c: str
1509+
def __init__(self, bc: Tuple[int, str]) -> None:
1510+
b, c = bc
1511+
self.__attrs_init__(b, c)
1512+
1513+
reveal_type(A) # N: Revealed type is "def (bc: Tuple[builtins.int, builtins.str]) -> __main__.A"
1514+
reveal_type(A.__init__) # N: Revealed type is "def (self: __main__.A, bc: Tuple[builtins.int, builtins.str])"
1515+
reveal_type(A.__attrs_init__) # N: Revealed type is "def (self: __main__.A, b: builtins.int, c: builtins.str)"
1516+
1517+
[builtins fixtures/attr.pyi]
1518+
15011519
[case testAttrsClassWithSlots]
15021520
import attr
15031521

0 commit comments

Comments
 (0)