@@ -103,6 +103,45 @@ def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
103
103
return _none_constructor , _args
104
104
105
105
106
+ class _Hashability (enum .Enum ):
107
+ """
108
+ The hashability of a class.
109
+ """
110
+
111
+ HASHABLE = "hashable" # write a __hash__
112
+ UNHASHABLE = "unhashable" # set __hash__ to None
113
+ LEAVE_ALONE = "leave_alone" # don't touch __hash__
114
+
115
+
116
+ class _AttrsParams (NamedTuple ):
117
+ """
118
+ Effective parameters to attrs() or define() decorators.
119
+ """
120
+
121
+ exception : bool
122
+ slots : bool
123
+ frozen : bool
124
+ init : bool
125
+ repr : bool
126
+ eq : bool
127
+ order : bool
128
+ hash : _Hashability
129
+ match_args : bool
130
+ kw_only : bool
131
+ force_kw_only : bool
132
+ weakref_slot : bool
133
+ auto_attribs : bool
134
+ collect_by_mro : bool
135
+ auto_detect : bool
136
+ auto_exc : bool
137
+ cache_hash : bool
138
+ str : bool
139
+ getstate_setstate : bool
140
+ has_custom_setattr : bool
141
+ on_setattr : Callable [[str , Any ], Any ]
142
+ field_transformer : Callable [[Attribute ], Attribute ]
143
+
144
+
106
145
def attrib (
107
146
default = NOTHING ,
108
147
validator = None ,
@@ -673,40 +712,28 @@ def __init__(
673
712
self ,
674
713
cls : type ,
675
714
these ,
676
- slots ,
677
- frozen ,
678
- weakref_slot ,
679
- getstate_setstate ,
680
- auto_attribs ,
681
- kw_only ,
682
- force_kw_only ,
683
- cache_hash ,
684
- is_exc ,
685
- collect_by_mro ,
686
- on_setattr ,
687
- has_custom_setattr ,
688
- field_transformer ,
715
+ attrs_params : _AttrsParams ,
689
716
):
690
717
attrs , base_attrs , base_map = _transform_attrs (
691
718
cls ,
692
719
these ,
693
- auto_attribs ,
694
- kw_only ,
695
- force_kw_only ,
696
- collect_by_mro ,
697
- field_transformer ,
720
+ attrs_params . auto_attribs ,
721
+ attrs_params . kw_only ,
722
+ attrs_params . force_kw_only ,
723
+ attrs_params . collect_by_mro ,
724
+ attrs_params . field_transformer ,
698
725
)
699
726
700
727
self ._cls = cls
701
- self ._cls_dict = dict (cls .__dict__ ) if slots else {}
728
+ self ._cls_dict = dict (cls .__dict__ ) if attrs_params . slots else {}
702
729
self ._attrs = attrs
703
730
self ._base_names = {a .name for a in base_attrs }
704
731
self ._base_attr_map = base_map
705
732
self ._attr_names = tuple (a .name for a in attrs )
706
- self ._slots = slots
707
- self ._frozen = frozen
708
- self ._weakref_slot = weakref_slot
709
- self ._cache_hash = cache_hash
733
+ self ._slots = attrs_params . slots
734
+ self ._frozen = attrs_params . frozen
735
+ self ._weakref_slot = attrs_params . weakref_slot
736
+ self ._cache_hash = attrs_params . cache_hash
710
737
self ._has_pre_init = bool (getattr (cls , "__attrs_pre_init__" , False ))
711
738
self ._pre_init_has_args = False
712
739
if self ._has_pre_init :
@@ -717,20 +744,21 @@ def __init__(
717
744
self ._pre_init_has_args = len (pre_init_signature .parameters ) > 1
718
745
self ._has_post_init = bool (getattr (cls , "__attrs_post_init__" , False ))
719
746
self ._delete_attribs = not bool (these )
720
- self ._is_exc = is_exc
721
- self ._on_setattr = on_setattr
747
+ self ._is_exc = attrs_params . exception
748
+ self ._on_setattr = attrs_params . on_setattr
722
749
723
- self ._has_custom_setattr = has_custom_setattr
750
+ self ._has_custom_setattr = attrs_params . has_custom_setattr
724
751
self ._wrote_own_setattr = False
725
752
726
753
self ._cls_dict ["__attrs_attrs__" ] = self ._attrs
754
+ self ._cls_dict ["__attrs_params__" ] = attrs_params
727
755
728
- if frozen :
756
+ if attrs_params . frozen :
729
757
self ._cls_dict ["__setattr__" ] = _frozen_setattrs
730
758
self ._cls_dict ["__delattr__" ] = _frozen_delattrs
731
759
732
760
self ._wrote_own_setattr = True
733
- elif on_setattr in (
761
+ elif self . _on_setattr in (
734
762
_DEFAULT_ON_SETATTR ,
735
763
setters .validate ,
736
764
setters .convert ,
@@ -746,18 +774,18 @@ def __init__(
746
774
break
747
775
if (
748
776
(
749
- on_setattr == _DEFAULT_ON_SETATTR
777
+ self . _on_setattr == _DEFAULT_ON_SETATTR
750
778
and not (has_validator or has_converter )
751
779
)
752
- or (on_setattr == setters .validate and not has_validator )
753
- or (on_setattr == setters .convert and not has_converter )
780
+ or (self . _on_setattr == setters .validate and not has_validator )
781
+ or (self . _on_setattr == setters .convert and not has_converter )
754
782
):
755
783
# If class-level on_setattr is set to convert + validate, but
756
784
# there's no field to convert or validate, pretend like there's
757
785
# no on_setattr.
758
786
self ._on_setattr = None
759
787
760
- if getstate_setstate :
788
+ if attrs_params . getstate_setstate :
761
789
(
762
790
self ._cls_dict ["__getstate__" ],
763
791
self ._cls_dict ["__setstate__" ],
@@ -1456,6 +1484,7 @@ def attrs(
1456
1484
on_setattr = setters .pipe (* on_setattr )
1457
1485
1458
1486
def wrap (cls ):
1487
+ nonlocal hash
1459
1488
is_frozen = frozen or _has_frozen_base_class (cls )
1460
1489
is_exc = auto_exc is True and issubclass (cls , BaseException )
1461
1490
has_own_setattr = auto_detect and _has_own_attribute (
@@ -1466,85 +1495,96 @@ def wrap(cls):
1466
1495
msg = "Can't freeze a class with a custom __setattr__."
1467
1496
raise ValueError (msg )
1468
1497
1469
- builder = _ClassBuilder (
1470
- cls ,
1471
- these ,
1472
- slots ,
1473
- is_frozen ,
1474
- weakref_slot ,
1475
- _determine_whether_to_implement (
1498
+ eq = not is_exc and _determine_whether_to_implement (
1499
+ cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1500
+ )
1501
+
1502
+ if is_exc :
1503
+ hashability = _Hashability .LEAVE_ALONE
1504
+ elif hash is True :
1505
+ hashability = _Hashability .HASHABLE
1506
+ elif hash is False :
1507
+ hashability = _Hashability .LEAVE_ALONE
1508
+ elif hash is None :
1509
+ if auto_detect is True and _has_own_attribute (cls , "__hash__" ):
1510
+ hashability = _Hashability .LEAVE_ALONE
1511
+ elif eq is True and is_frozen is True :
1512
+ hashability = _Hashability .HASHABLE
1513
+ elif eq is False :
1514
+ hashability = _Hashability .LEAVE_ALONE
1515
+ else :
1516
+ hashability = _Hashability .UNHASHABLE
1517
+ else :
1518
+ msg = "Invalid value for hash. Must be True, False, or None."
1519
+ raise TypeError (msg )
1520
+
1521
+ if hashability is not _Hashability .HASHABLE and cache_hash :
1522
+ msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1523
+ raise TypeError (msg )
1524
+
1525
+ attrs_params = _AttrsParams (
1526
+ exception = is_exc ,
1527
+ frozen = is_frozen ,
1528
+ slots = slots ,
1529
+ init = _determine_whether_to_implement (
1530
+ cls , init , auto_detect , ("__init__" ,)
1531
+ ),
1532
+ repr = _determine_whether_to_implement (
1533
+ cls , repr , auto_detect , ("__repr__" ,)
1534
+ ),
1535
+ eq = eq ,
1536
+ order = not is_exc
1537
+ and _determine_whether_to_implement (
1538
+ cls ,
1539
+ order_ ,
1540
+ auto_detect ,
1541
+ ("__lt__" , "__le__" , "__gt__" , "__ge__" ),
1542
+ ),
1543
+ hash = hashability ,
1544
+ match_args = match_args ,
1545
+ kw_only = kw_only ,
1546
+ force_kw_only = force_kw_only ,
1547
+ weakref_slot = weakref_slot ,
1548
+ auto_attribs = auto_attribs if auto_attribs is not None else False ,
1549
+ collect_by_mro = collect_by_mro ,
1550
+ auto_detect = auto_detect ,
1551
+ auto_exc = is_exc ,
1552
+ cache_hash = cache_hash ,
1553
+ str = str ,
1554
+ getstate_setstate = _determine_whether_to_implement (
1476
1555
cls ,
1477
1556
getstate_setstate ,
1478
1557
auto_detect ,
1479
1558
("__getstate__" , "__setstate__" ),
1480
1559
default = slots ,
1481
1560
),
1482
- auto_attribs ,
1483
- kw_only ,
1484
- force_kw_only ,
1485
- cache_hash ,
1486
- is_exc ,
1487
- collect_by_mro ,
1488
- on_setattr ,
1489
- has_own_setattr ,
1490
- field_transformer ,
1561
+ has_custom_setattr = has_own_setattr ,
1562
+ on_setattr = on_setattr ,
1563
+ field_transformer = field_transformer ,
1491
1564
)
1492
1565
1493
- if _determine_whether_to_implement (
1494
- cls , repr , auto_detect , ( "__repr__" ,)
1495
- ) :
1566
+ builder = _ClassBuilder ( cls , these , attrs_params )
1567
+
1568
+ if attrs_params . repr is True :
1496
1569
builder .add_repr (repr_ns )
1497
1570
1498
- if str is True :
1571
+ if attrs_params . str is True :
1499
1572
builder .add_str ()
1500
1573
1501
- eq = _determine_whether_to_implement (
1502
- cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1503
- )
1504
- if not is_exc and eq is True :
1574
+ if attrs_params .eq is True :
1505
1575
builder .add_eq ()
1506
- if not is_exc and _determine_whether_to_implement (
1507
- cls , order_ , auto_detect , ("__lt__" , "__le__" , "__gt__" , "__ge__" )
1508
- ):
1576
+ if attrs_params .order is True :
1509
1577
builder .add_order ()
1510
1578
1511
1579
if not frozen :
1512
1580
builder .add_setattr ()
1513
1581
1514
- nonlocal hash
1515
- if (
1516
- hash is None
1517
- and auto_detect is True
1518
- and _has_own_attribute (cls , "__hash__" )
1519
- ):
1520
- hash = False
1521
-
1522
- if hash is not True and hash is not False and hash is not None :
1523
- # Can't use `hash in` because 1 == True for example.
1524
- msg = "Invalid value for hash. Must be True, False, or None."
1525
- raise TypeError (msg )
1526
-
1527
- if hash is False or (hash is None and eq is False ) or is_exc :
1528
- # Don't do anything. Should fall back to __object__'s __hash__
1529
- # which is by id.
1530
- if cache_hash :
1531
- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1532
- raise TypeError (msg )
1533
- elif hash is True or (
1534
- hash is None and eq is True and is_frozen is True
1535
- ):
1536
- # Build a __hash__ if told so, or if it's safe.
1582
+ if attrs_params .hash is _Hashability .HASHABLE :
1537
1583
builder .add_hash ()
1538
- else :
1539
- # Raise TypeError on attempts to hash.
1540
- if cache_hash :
1541
- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1542
- raise TypeError (msg )
1584
+ elif attrs_params .hash is _Hashability .UNHASHABLE :
1543
1585
builder .make_unhashable ()
1544
1586
1545
- if _determine_whether_to_implement (
1546
- cls , init , auto_detect , ("__init__" ,)
1547
- ):
1587
+ if attrs_params .init :
1548
1588
builder .add_init ()
1549
1589
else :
1550
1590
builder .add_attrs_init ()
0 commit comments