Skip to content

Commit 0166090

Browse files
committed
Implement _AttrsParams
ref #602
1 parent b8c63c3 commit 0166090

File tree

3 files changed

+384
-129
lines changed

3 files changed

+384
-129
lines changed

src/attr/_make.py

Lines changed: 130 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,45 @@ 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+
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+
106145
def attrib(
107146
default=NOTHING,
108147
validator=None,
@@ -673,40 +712,28 @@ def __init__(
673712
self,
674713
cls: type,
675714
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,
689716
):
690717
attrs, base_attrs, base_map = _transform_attrs(
691718
cls,
692719
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,
698725
)
699726

700727
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 {}
702729
self._attrs = attrs
703730
self._base_names = {a.name for a in base_attrs}
704731
self._base_attr_map = base_map
705732
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
710737
self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
711738
self._pre_init_has_args = False
712739
if self._has_pre_init:
@@ -717,20 +744,21 @@ def __init__(
717744
self._pre_init_has_args = len(pre_init_signature.parameters) > 1
718745
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
719746
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
722749

723-
self._has_custom_setattr = has_custom_setattr
750+
self._has_custom_setattr = attrs_params.has_custom_setattr
724751
self._wrote_own_setattr = False
725752

726753
self._cls_dict["__attrs_attrs__"] = self._attrs
754+
self._cls_dict["__attrs_params__"] = attrs_params
727755

728-
if frozen:
756+
if attrs_params.frozen:
729757
self._cls_dict["__setattr__"] = _frozen_setattrs
730758
self._cls_dict["__delattr__"] = _frozen_delattrs
731759

732760
self._wrote_own_setattr = True
733-
elif on_setattr in (
761+
elif self._on_setattr in (
734762
_DEFAULT_ON_SETATTR,
735763
setters.validate,
736764
setters.convert,
@@ -746,18 +774,18 @@ def __init__(
746774
break
747775
if (
748776
(
749-
on_setattr == _DEFAULT_ON_SETATTR
777+
self._on_setattr == _DEFAULT_ON_SETATTR
750778
and not (has_validator or has_converter)
751779
)
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)
754782
):
755783
# If class-level on_setattr is set to convert + validate, but
756784
# there's no field to convert or validate, pretend like there's
757785
# no on_setattr.
758786
self._on_setattr = None
759787

760-
if getstate_setstate:
788+
if attrs_params.getstate_setstate:
761789
(
762790
self._cls_dict["__getstate__"],
763791
self._cls_dict["__setstate__"],
@@ -1456,6 +1484,7 @@ def attrs(
14561484
on_setattr = setters.pipe(*on_setattr)
14571485

14581486
def wrap(cls):
1487+
nonlocal hash
14591488
is_frozen = frozen or _has_frozen_base_class(cls)
14601489
is_exc = auto_exc is True and issubclass(cls, BaseException)
14611490
has_own_setattr = auto_detect and _has_own_attribute(
@@ -1466,85 +1495,96 @@ def wrap(cls):
14661495
msg = "Can't freeze a class with a custom __setattr__."
14671496
raise ValueError(msg)
14681497

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(
14761555
cls,
14771556
getstate_setstate,
14781557
auto_detect,
14791558
("__getstate__", "__setstate__"),
14801559
default=slots,
14811560
),
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,
14911564
)
14921565

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:
14961569
builder.add_repr(repr_ns)
14971570

1498-
if str is True:
1571+
if attrs_params.str is True:
14991572
builder.add_str()
15001573

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:
15051575
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:
15091577
builder.add_order()
15101578

15111579
if not frozen:
15121580
builder.add_setattr()
15131581

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:
15371583
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:
15431585
builder.make_unhashable()
15441586

1545-
if _determine_whether_to_implement(
1546-
cls, init, auto_detect, ("__init__",)
1547-
):
1587+
if attrs_params.init:
15481588
builder.add_init()
15491589
else:
15501590
builder.add_attrs_init()

0 commit comments

Comments
 (0)