Skip to content

Commit af0f118

Browse files
committed
Implement _AttrsParams
ref #602
1 parent 63187cc commit af0f118

File tree

3 files changed

+374
-123
lines changed

3 files changed

+374
-123
lines changed

src/attr/_make.py

Lines changed: 127 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,44 @@ def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
103103
return _none_constructor, _args
104104

105105

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+
106144
def attrib(
107145
default=NOTHING,
108146
validator=None,
@@ -663,38 +701,27 @@ def __init__(
663701
self,
664702
cls: type,
665703
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,
678705
):
679706
attrs, base_attrs, base_map = _transform_attrs(
680707
cls,
681708
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,
686713
)
687714

688715
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 {}
690717
self._attrs = attrs
691718
self._base_names = {a.name for a in base_attrs}
692719
self._base_attr_map = base_map
693720
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
698725
self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
699726
self._pre_init_has_args = False
700727
if self._has_pre_init:
@@ -705,20 +732,21 @@ def __init__(
705732
self._pre_init_has_args = len(pre_init_signature.parameters) > 1
706733
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
707734
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
710737

711-
self._has_custom_setattr = has_custom_setattr
738+
self._has_custom_setattr = attrs_params.has_custom_setattr
712739
self._wrote_own_setattr = False
713740

714741
self._cls_dict["__attrs_attrs__"] = self._attrs
742+
self._cls_dict["__attrs_params__"] = attrs_params
715743

716-
if frozen:
744+
if attrs_params.frozen:
717745
self._cls_dict["__setattr__"] = _frozen_setattrs
718746
self._cls_dict["__delattr__"] = _frozen_delattrs
719747

720748
self._wrote_own_setattr = True
721-
elif on_setattr in (
749+
elif self._on_setattr in (
722750
_DEFAULT_ON_SETATTR,
723751
setters.validate,
724752
setters.convert,
@@ -734,18 +762,18 @@ def __init__(
734762
break
735763
if (
736764
(
737-
on_setattr == _DEFAULT_ON_SETATTR
765+
self._on_setattr == _DEFAULT_ON_SETATTR
738766
and not (has_validator or has_converter)
739767
)
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)
742770
):
743771
# If class-level on_setattr is set to convert + validate, but
744772
# there's no field to convert or validate, pretend like there's
745773
# no on_setattr.
746774
self._on_setattr = None
747775

748-
if getstate_setstate:
776+
if attrs_params.getstate_setstate:
749777
(
750778
self._cls_dict["__getstate__"],
751779
self._cls_dict["__setstate__"],
@@ -1439,6 +1467,7 @@ def attrs(
14391467
on_setattr = setters.pipe(*on_setattr)
14401468

14411469
def wrap(cls):
1470+
nonlocal hash
14421471
is_frozen = frozen or _has_frozen_base_class(cls)
14431472
is_exc = auto_exc is True and issubclass(cls, BaseException)
14441473
has_own_setattr = auto_detect and _has_own_attribute(
@@ -1449,84 +1478,95 @@ def wrap(cls):
14491478
msg = "Can't freeze a class with a custom __setattr__."
14501479
raise ValueError(msg)
14511480

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(
14591537
cls,
14601538
getstate_setstate,
14611539
auto_detect,
14621540
("__getstate__", "__setstate__"),
14631541
default=slots,
14641542
),
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,
14731546
)
14741547

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:
14781551
builder.add_repr(repr_ns)
14791552

1480-
if str is True:
1553+
if attrs_params.str is True:
14811554
builder.add_str()
14821555

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:
14871557
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:
14911559
builder.add_order()
14921560

14931561
if not frozen:
14941562
builder.add_setattr()
14951563

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:
15191565
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:
15251567
builder.make_unhashable()
15261568

1527-
if _determine_whether_to_implement(
1528-
cls, init, auto_detect, ("__init__",)
1529-
):
1569+
if attrs_params.init:
15301570
builder.add_init()
15311571
else:
15321572
builder.add_attrs_init()

0 commit comments

Comments
 (0)