@@ -424,13 +424,14 @@ def _get_frozen(ctx: mypy.plugin.ClassDefContext, frozen_default: bool) -> bool:
424424
425425
426426def _analyze_class (
427- ctx : mypy .plugin .ClassDefContext , auto_attribs : bool | None , kw_only : bool
427+ ctx : mypy .plugin .ClassDefContext , auto_attribs : bool | None , class_kw_only : bool
428428) -> list [Attribute ]:
429429 """Analyze the class body of an attr maker, its parents, and return the Attributes found.
430430
431431 auto_attribs=True means we'll generate attributes from type annotations also.
432432 auto_attribs=None means we'll detect which mode to use.
433- kw_only=True means that all attributes created here will be keyword only args in __init__.
433+ class_kw_only=True means that all attributes created here will be keyword only args by
434+ default in __init__.
434435 """
435436 own_attrs : dict [str , Attribute ] = {}
436437 if auto_attribs is None :
@@ -439,7 +440,7 @@ def _analyze_class(
439440 # Walk the body looking for assignments and decorators.
440441 for stmt in ctx .cls .defs .body :
441442 if isinstance (stmt , AssignmentStmt ):
442- for attr in _attributes_from_assignment (ctx , stmt , auto_attribs , kw_only ):
443+ for attr in _attributes_from_assignment (ctx , stmt , auto_attribs , class_kw_only ):
443444 # When attrs are defined twice in the same body we want to use the 2nd definition
444445 # in the 2nd location. So remove it from the OrderedDict.
445446 # Unless it's auto_attribs in which case we want the 2nd definition in the
@@ -537,7 +538,7 @@ def _detect_auto_attribs(ctx: mypy.plugin.ClassDefContext) -> bool:
537538
538539
539540def _attributes_from_assignment (
540- ctx : mypy .plugin .ClassDefContext , stmt : AssignmentStmt , auto_attribs : bool , kw_only : bool
541+ ctx : mypy .plugin .ClassDefContext , stmt : AssignmentStmt , auto_attribs : bool , class_kw_only : bool
541542) -> Iterable [Attribute ]:
542543 """Return Attribute objects that are created by this assignment.
543544
@@ -565,11 +566,13 @@ def _attributes_from_assignment(
565566 and isinstance (rvalue .callee , RefExpr )
566567 and rvalue .callee .fullname in attr_attrib_makers
567568 ):
568- attr = _attribute_from_attrib_maker (ctx , auto_attribs , kw_only , lhs , rvalue , stmt )
569+ attr = _attribute_from_attrib_maker (
570+ ctx , auto_attribs , class_kw_only , lhs , rvalue , stmt
571+ )
569572 if attr :
570573 yield attr
571574 elif auto_attribs and stmt .type and stmt .new_syntax and not is_class_var (lhs ):
572- yield _attribute_from_auto_attrib (ctx , kw_only , lhs , rvalue , stmt )
575+ yield _attribute_from_auto_attrib (ctx , class_kw_only , lhs , rvalue , stmt )
573576
574577
575578def _cleanup_decorator (stmt : Decorator , attr_map : dict [str , Attribute ]) -> None :
@@ -604,7 +607,7 @@ def _cleanup_decorator(stmt: Decorator, attr_map: dict[str, Attribute]) -> None:
604607
605608def _attribute_from_auto_attrib (
606609 ctx : mypy .plugin .ClassDefContext ,
607- kw_only : bool ,
610+ class_kw_only : bool ,
608611 lhs : NameExpr ,
609612 rvalue : Expression ,
610613 stmt : AssignmentStmt ,
@@ -615,13 +618,13 @@ def _attribute_from_auto_attrib(
615618 has_rhs = not isinstance (rvalue , TempNode )
616619 sym = ctx .cls .info .names .get (name )
617620 init_type = sym .type if sym else None
618- return Attribute (name , None , ctx .cls .info , has_rhs , True , kw_only , None , stmt , init_type )
621+ return Attribute (name , None , ctx .cls .info , has_rhs , True , class_kw_only , None , stmt , init_type )
619622
620623
621624def _attribute_from_attrib_maker (
622625 ctx : mypy .plugin .ClassDefContext ,
623626 auto_attribs : bool ,
624- kw_only : bool ,
627+ class_kw_only : bool ,
625628 lhs : NameExpr ,
626629 rvalue : CallExpr ,
627630 stmt : AssignmentStmt ,
@@ -642,9 +645,9 @@ def _attribute_from_attrib_maker(
642645
643646 # Read all the arguments from the call.
644647 init = _get_bool_argument (ctx , rvalue , "init" , True )
645- # Note: If the class decorator says kw_only=True the attribute is ignored.
646- # See https://github.com/python-attrs/attrs/issues/481 for explanation.
647- kw_only | = _get_bool_argument (ctx , rvalue , "kw_only" , False )
648+ # The class decorator kw_only value can be overridden by the attribute value
649+ # See https://github.com/python-attrs/attrs/pull/1457
650+ kw_only = _get_bool_argument (ctx , rvalue , "kw_only" , class_kw_only )
648651
649652 # TODO: Check for attr.NOTHING
650653 attr_has_default = bool (_get_argument (rvalue , "default" ))
0 commit comments