@@ -103,6 +103,44 @@ 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
+ weakref_slot : bool
132
+ auto_attribs : bool
133
+ collect_by_mro : bool
134
+ auto_detect : bool
135
+ auto_exc : bool
136
+ cache_hash : bool
137
+ str : bool
138
+ getstate_setstate : bool
139
+ has_custom_setattr : bool
140
+ on_setattr : Callable [[str , Any ], Any ]
141
+ field_transformer : Callable [[Attribute ], Attribute ]
142
+
143
+
106
144
def attrib (
107
145
default = NOTHING ,
108
146
validator = None ,
@@ -663,38 +701,27 @@ def __init__(
663
701
self ,
664
702
cls : type ,
665
703
these ,
666
- slots ,
667
- frozen ,
668
- weakref_slot ,
669
- getstate_setstate ,
670
- auto_attribs ,
671
- kw_only ,
672
- cache_hash ,
673
- is_exc ,
674
- collect_by_mro ,
675
- on_setattr ,
676
- has_custom_setattr ,
677
- field_transformer ,
704
+ attrs_params : _AttrsParams ,
678
705
):
679
706
attrs , base_attrs , base_map = _transform_attrs (
680
707
cls ,
681
708
these ,
682
- auto_attribs ,
683
- kw_only ,
684
- collect_by_mro ,
685
- field_transformer ,
709
+ attrs_params . auto_attribs ,
710
+ attrs_params . kw_only ,
711
+ attrs_params . collect_by_mro ,
712
+ attrs_params . field_transformer ,
686
713
)
687
714
688
715
self ._cls = cls
689
- self ._cls_dict = dict (cls .__dict__ ) if slots else {}
716
+ self ._cls_dict = dict (cls .__dict__ ) if attrs_params . slots else {}
690
717
self ._attrs = attrs
691
718
self ._base_names = {a .name for a in base_attrs }
692
719
self ._base_attr_map = base_map
693
720
self ._attr_names = tuple (a .name for a in attrs )
694
- self ._slots = slots
695
- self ._frozen = frozen
696
- self ._weakref_slot = weakref_slot
697
- self ._cache_hash = cache_hash
721
+ self ._slots = attrs_params . slots
722
+ self ._frozen = attrs_params . frozen
723
+ self ._weakref_slot = attrs_params . weakref_slot
724
+ self ._cache_hash = attrs_params . cache_hash
698
725
self ._has_pre_init = bool (getattr (cls , "__attrs_pre_init__" , False ))
699
726
self ._pre_init_has_args = False
700
727
if self ._has_pre_init :
@@ -705,20 +732,21 @@ def __init__(
705
732
self ._pre_init_has_args = len (pre_init_signature .parameters ) > 1
706
733
self ._has_post_init = bool (getattr (cls , "__attrs_post_init__" , False ))
707
734
self ._delete_attribs = not bool (these )
708
- self ._is_exc = is_exc
709
- self ._on_setattr = on_setattr
735
+ self ._is_exc = attrs_params . exception
736
+ self ._on_setattr = attrs_params . on_setattr
710
737
711
- self ._has_custom_setattr = has_custom_setattr
738
+ self ._has_custom_setattr = attrs_params . has_custom_setattr
712
739
self ._wrote_own_setattr = False
713
740
714
741
self ._cls_dict ["__attrs_attrs__" ] = self ._attrs
742
+ self ._cls_dict ["__attrs_params__" ] = attrs_params
715
743
716
- if frozen :
744
+ if attrs_params . frozen :
717
745
self ._cls_dict ["__setattr__" ] = _frozen_setattrs
718
746
self ._cls_dict ["__delattr__" ] = _frozen_delattrs
719
747
720
748
self ._wrote_own_setattr = True
721
- elif on_setattr in (
749
+ elif self . _on_setattr in (
722
750
_DEFAULT_ON_SETATTR ,
723
751
setters .validate ,
724
752
setters .convert ,
@@ -734,18 +762,18 @@ def __init__(
734
762
break
735
763
if (
736
764
(
737
- on_setattr == _DEFAULT_ON_SETATTR
765
+ self . _on_setattr == _DEFAULT_ON_SETATTR
738
766
and not (has_validator or has_converter )
739
767
)
740
- or (on_setattr == setters .validate and not has_validator )
741
- or (on_setattr == setters .convert and not has_converter )
768
+ or (self . _on_setattr == setters .validate and not has_validator )
769
+ or (self . _on_setattr == setters .convert and not has_converter )
742
770
):
743
771
# If class-level on_setattr is set to convert + validate, but
744
772
# there's no field to convert or validate, pretend like there's
745
773
# no on_setattr.
746
774
self ._on_setattr = None
747
775
748
- if getstate_setstate :
776
+ if attrs_params . getstate_setstate :
749
777
(
750
778
self ._cls_dict ["__getstate__" ],
751
779
self ._cls_dict ["__setstate__" ],
@@ -1439,6 +1467,7 @@ def attrs(
1439
1467
on_setattr = setters .pipe (* on_setattr )
1440
1468
1441
1469
def wrap (cls ):
1470
+ nonlocal hash
1442
1471
is_frozen = frozen or _has_frozen_base_class (cls )
1443
1472
is_exc = auto_exc is True and issubclass (cls , BaseException )
1444
1473
has_own_setattr = auto_detect and _has_own_attribute (
@@ -1449,84 +1478,95 @@ def wrap(cls):
1449
1478
msg = "Can't freeze a class with a custom __setattr__."
1450
1479
raise ValueError (msg )
1451
1480
1452
- builder = _ClassBuilder (
1453
- cls ,
1454
- these ,
1455
- slots ,
1456
- is_frozen ,
1457
- weakref_slot ,
1458
- _determine_whether_to_implement (
1481
+ eq = not is_exc and _determine_whether_to_implement (
1482
+ cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1483
+ )
1484
+
1485
+ if is_exc :
1486
+ hashability = _Hashability .LEAVE_ALONE
1487
+ elif hash is True :
1488
+ hashability = _Hashability .HASHABLE
1489
+ elif hash is False :
1490
+ hashability = _Hashability .LEAVE_ALONE
1491
+ elif hash is None :
1492
+ if auto_detect is True and _has_own_attribute (cls , "__hash__" ):
1493
+ hashability = _Hashability .LEAVE_ALONE
1494
+ elif eq is True and is_frozen is True :
1495
+ hashability = _Hashability .HASHABLE
1496
+ elif eq is False :
1497
+ hashability = _Hashability .LEAVE_ALONE
1498
+ else :
1499
+ hashability = _Hashability .UNHASHABLE
1500
+ else :
1501
+ msg = "Invalid value for hash. Must be True, False, or None."
1502
+ raise TypeError (msg )
1503
+
1504
+ if hashability is not _Hashability .HASHABLE and cache_hash :
1505
+ msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1506
+ raise TypeError (msg )
1507
+
1508
+ attrs_params = _AttrsParams (
1509
+ exception = is_exc ,
1510
+ frozen = is_frozen ,
1511
+ slots = slots ,
1512
+ init = _determine_whether_to_implement (
1513
+ cls , init , auto_detect , ("__init__" ,)
1514
+ ),
1515
+ repr = _determine_whether_to_implement (
1516
+ cls , repr , auto_detect , ("__repr__" ,)
1517
+ ),
1518
+ eq = eq ,
1519
+ order = not is_exc
1520
+ and _determine_whether_to_implement (
1521
+ cls ,
1522
+ order_ ,
1523
+ auto_detect ,
1524
+ ("__lt__" , "__le__" , "__gt__" , "__ge__" ),
1525
+ ),
1526
+ hash = hashability ,
1527
+ match_args = match_args ,
1528
+ kw_only = kw_only ,
1529
+ weakref_slot = weakref_slot ,
1530
+ auto_attribs = auto_attribs if auto_attribs is not None else False ,
1531
+ collect_by_mro = collect_by_mro ,
1532
+ auto_detect = auto_detect ,
1533
+ auto_exc = is_exc ,
1534
+ cache_hash = cache_hash ,
1535
+ str = str ,
1536
+ getstate_setstate = _determine_whether_to_implement (
1459
1537
cls ,
1460
1538
getstate_setstate ,
1461
1539
auto_detect ,
1462
1540
("__getstate__" , "__setstate__" ),
1463
1541
default = slots ,
1464
1542
),
1465
- auto_attribs ,
1466
- kw_only ,
1467
- cache_hash ,
1468
- is_exc ,
1469
- collect_by_mro ,
1470
- on_setattr ,
1471
- has_own_setattr ,
1472
- field_transformer ,
1543
+ has_custom_setattr = has_own_setattr ,
1544
+ on_setattr = on_setattr ,
1545
+ field_transformer = field_transformer ,
1473
1546
)
1474
1547
1475
- if _determine_whether_to_implement (
1476
- cls , repr , auto_detect , ( "__repr__" ,)
1477
- ) :
1548
+ builder = _ClassBuilder ( cls , these , attrs_params )
1549
+
1550
+ if attrs_params . repr is True :
1478
1551
builder .add_repr (repr_ns )
1479
1552
1480
- if str is True :
1553
+ if attrs_params . str is True :
1481
1554
builder .add_str ()
1482
1555
1483
- eq = _determine_whether_to_implement (
1484
- cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1485
- )
1486
- if not is_exc and eq is True :
1556
+ if attrs_params .eq is True :
1487
1557
builder .add_eq ()
1488
- if not is_exc and _determine_whether_to_implement (
1489
- cls , order_ , auto_detect , ("__lt__" , "__le__" , "__gt__" , "__ge__" )
1490
- ):
1558
+ if attrs_params .order is True :
1491
1559
builder .add_order ()
1492
1560
1493
1561
if not frozen :
1494
1562
builder .add_setattr ()
1495
1563
1496
- nonlocal hash
1497
- if (
1498
- hash is None
1499
- and auto_detect is True
1500
- and _has_own_attribute (cls , "__hash__" )
1501
- ):
1502
- hash = False
1503
-
1504
- if hash is not True and hash is not False and hash is not None :
1505
- # Can't use `hash in` because 1 == True for example.
1506
- msg = "Invalid value for hash. Must be True, False, or None."
1507
- raise TypeError (msg )
1508
-
1509
- if hash is False or (hash is None and eq is False ) or is_exc :
1510
- # Don't do anything. Should fall back to __object__'s __hash__
1511
- # which is by id.
1512
- if cache_hash :
1513
- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1514
- raise TypeError (msg )
1515
- elif hash is True or (
1516
- hash is None and eq is True and is_frozen is True
1517
- ):
1518
- # Build a __hash__ if told so, or if it's safe.
1564
+ if attrs_params .hash is _Hashability .HASHABLE :
1519
1565
builder .add_hash ()
1520
- else :
1521
- # Raise TypeError on attempts to hash.
1522
- if cache_hash :
1523
- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1524
- raise TypeError (msg )
1566
+ elif attrs_params .hash is _Hashability .UNHASHABLE :
1525
1567
builder .make_unhashable ()
1526
1568
1527
- if _determine_whether_to_implement (
1528
- cls , init , auto_detect , ("__init__" ,)
1529
- ):
1569
+ if attrs_params .init :
1530
1570
builder .add_init ()
1531
1571
else :
1532
1572
builder .add_attrs_init ()
0 commit comments