@@ -114,7 +114,7 @@ def attrib(
114114 type = None ,
115115 converter = None ,
116116 factory = None ,
117- kw_only = False ,
117+ kw_only = None ,
118118 eq = None ,
119119 order = None ,
120120 on_setattr = None ,
@@ -157,6 +157,9 @@ def attrib(
157157 *eq*, *order*, and *cmp* also accept a custom callable
158158 .. versionchanged:: 21.1.0 *cmp* undeprecated
159159 .. versionadded:: 22.2.0 *alias*
160+ .. versionchanged:: 25.4.0
161+ *kw_only* can now be None, and its default is also changed from False to
162+ None.
160163 """
161164 eq , eq_key , order , order_key = _determine_attrib_eq_order (
162165 cmp , eq , order , True
@@ -374,7 +377,13 @@ def _collect_base_attrs_broken(cls, taken_attr_names):
374377
375378
376379def _transform_attrs (
377- cls , these , auto_attribs , kw_only , collect_by_mro , field_transformer
380+ cls ,
381+ these ,
382+ auto_attribs ,
383+ kw_only ,
384+ force_kw_only ,
385+ collect_by_mro ,
386+ field_transformer ,
378387) -> _Attributes :
379388 """
380389 Transform all `_CountingAttr`s on a class into `Attribute`s.
@@ -430,7 +439,8 @@ def _transform_attrs(
430439
431440 fca = Attribute .from_counting_attr
432441 own_attrs = [
433- fca (attr_name , ca , anns .get (attr_name )) for attr_name , ca in ca_list
442+ fca (attr_name , ca , kw_only , anns .get (attr_name ))
443+ for attr_name , ca in ca_list
434444 ]
435445
436446 if collect_by_mro :
@@ -442,7 +452,7 @@ def _transform_attrs(
442452 cls , {a .name for a in own_attrs }
443453 )
444454
445- if kw_only :
455+ if kw_only and force_kw_only :
446456 own_attrs = [a .evolve (kw_only = True ) for a in own_attrs ]
447457 base_attrs = [a .evolve (kw_only = True ) for a in base_attrs ]
448458
@@ -669,6 +679,7 @@ def __init__(
669679 getstate_setstate ,
670680 auto_attribs ,
671681 kw_only ,
682+ force_kw_only ,
672683 cache_hash ,
673684 is_exc ,
674685 collect_by_mro ,
@@ -681,6 +692,7 @@ def __init__(
681692 these ,
682693 auto_attribs ,
683694 kw_only ,
695+ force_kw_only ,
684696 collect_by_mro ,
685697 field_transformer ,
686698 )
@@ -1352,6 +1364,7 @@ def attrs(
13521364 field_transformer = None ,
13531365 match_args = True ,
13541366 unsafe_hash = None ,
1367+ force_kw_only = True ,
13551368):
13561369 r"""
13571370 A class decorator that adds :term:`dunder methods` according to the
@@ -1418,6 +1431,10 @@ def attrs(
14181431 If a class has an *inherited* classmethod called
14191432 ``__attrs_init_subclass__``, it is executed after the class is created.
14201433 .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1434+ .. versionchanged:: 25.4.0
1435+ *kw_only* now only applies to attributes defined in the current class,
1436+ and respects attribute-level ``kw_only=False`` settings.
1437+ .. versionadded:: 25.4.0 *force_kw_only*
14211438 """
14221439 if repr_ns is not None :
14231440 import warnings
@@ -1464,6 +1481,7 @@ def wrap(cls):
14641481 ),
14651482 auto_attribs ,
14661483 kw_only ,
1484+ force_kw_only ,
14671485 cache_hash ,
14681486 is_exc ,
14691487 collect_by_mro ,
@@ -2516,7 +2534,11 @@ def __setattr__(self, name, value):
25162534 raise FrozenInstanceError
25172535
25182536 @classmethod
2519- def from_counting_attr (cls , name : str , ca : _CountingAttr , type = None ):
2537+ def from_counting_attr (
2538+ cls , name : str , ca : _CountingAttr , kw_only : bool , type = None
2539+ ):
2540+ # The 'kw_only' argument is the class-level setting, and is used if the
2541+ # attribute itself does not explicitly set 'kw_only'.
25202542 # type holds the annotated value. deal with conflicts:
25212543 if type is None :
25222544 type = ca .type
@@ -2535,7 +2557,7 @@ def from_counting_attr(cls, name: str, ca: _CountingAttr, type=None):
25352557 ca .metadata ,
25362558 type ,
25372559 ca .converter ,
2538- ca .kw_only ,
2560+ kw_only if ca . kw_only is None else ca .kw_only ,
25392561 ca .eq ,
25402562 ca .eq_key ,
25412563 ca .order ,
0 commit comments