Skip to content

Commit da90471

Browse files
committed
Make KeywordOnly an Enum
And make Hashability public
1 parent 83f51e9 commit da90471

File tree

3 files changed

+54
-50
lines changed

3 files changed

+54
-50
lines changed

src/attr/_make.py

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

105105

106-
class _Hashability(enum.Enum):
106+
class Hashability(enum.Enum):
107107
"""
108108
The hashability of a class.
109109
"""
@@ -113,6 +113,16 @@ class _Hashability(enum.Enum):
113113
LEAVE_ALONE = "leave_alone" # don't touch __hash__
114114

115115

116+
class KeywordOnly(enum.Enum):
117+
"""
118+
How attributes should be treated regarding keyword-only parameters.
119+
"""
120+
121+
NO = "no" # attributes are not keyword-only
122+
YES = "yes" # attributes in current class without kw_only=False are keyword-only
123+
FORCE = "force" # all attributes are keyword-only
124+
125+
116126
class ClassProps(NamedTuple):
117127
"""
118128
Effective class properties as derived from parameters to attr.s() or
@@ -125,14 +135,13 @@ class ClassProps(NamedTuple):
125135
is_slotted: bool
126136
has_weakref_slot: bool
127137
is_frozen: bool
128-
is_kw_only: bool
129-
force_kw_only: bool
138+
kw_only: KeywordOnly
130139
collect_by_mro: bool
131140
init: bool
132141
repr: bool
133142
eq: bool
134143
order: bool
135-
hash: _Hashability
144+
hash: Hashability
136145
cache_hash: bool
137146
match_args: bool
138147
str: bool
@@ -419,7 +428,6 @@ def _transform_attrs(
419428
these,
420429
auto_attribs,
421430
kw_only,
422-
force_kw_only,
423431
collect_by_mro,
424432
field_transformer,
425433
) -> _Attributes:
@@ -477,7 +485,7 @@ def _transform_attrs(
477485

478486
fca = Attribute.from_counting_attr
479487
own_attrs = [
480-
fca(attr_name, ca, kw_only, anns.get(attr_name))
488+
fca(attr_name, ca, kw_only is not KeywordOnly.NO, anns.get(attr_name))
481489
for attr_name, ca in ca_list
482490
]
483491

@@ -490,7 +498,7 @@ def _transform_attrs(
490498
cls, {a.name for a in own_attrs}
491499
)
492500

493-
if kw_only and force_kw_only:
501+
if kw_only is KeywordOnly.FORCE:
494502
own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
495503
base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
496504

@@ -708,8 +716,7 @@ def __init__(
708716
cls,
709717
these,
710718
auto_attribs,
711-
props.is_kw_only,
712-
props.force_kw_only,
719+
props.kw_only,
713720
props.collect_by_mro,
714721
props.field_transformer,
715722
)
@@ -1481,28 +1488,33 @@ def wrap(cls):
14811488
)
14821489

14831490
if is_exc:
1484-
hashability = _Hashability.LEAVE_ALONE
1491+
hashability = Hashability.LEAVE_ALONE
14851492
elif hash is True:
1486-
hashability = _Hashability.HASHABLE
1493+
hashability = Hashability.HASHABLE
14871494
elif hash is False:
1488-
hashability = _Hashability.LEAVE_ALONE
1495+
hashability = Hashability.LEAVE_ALONE
14891496
elif hash is None:
14901497
if auto_detect is True and _has_own_attribute(cls, "__hash__"):
1491-
hashability = _Hashability.LEAVE_ALONE
1498+
hashability = Hashability.LEAVE_ALONE
14921499
elif eq is True and is_frozen is True:
1493-
hashability = _Hashability.HASHABLE
1500+
hashability = Hashability.HASHABLE
14941501
elif eq is False:
1495-
hashability = _Hashability.LEAVE_ALONE
1502+
hashability = Hashability.LEAVE_ALONE
14961503
else:
1497-
hashability = _Hashability.UNHASHABLE
1504+
hashability = Hashability.UNHASHABLE
14981505
else:
14991506
msg = "Invalid value for hash. Must be True, False, or None."
15001507
raise TypeError(msg)
15011508

1502-
if hashability is not _Hashability.HASHABLE and cache_hash:
1509+
if hashability is not Hashability.HASHABLE and cache_hash:
15031510
msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
15041511
raise TypeError(msg)
15051512

1513+
if kw_only:
1514+
kwo = KeywordOnly.YES if not force_kw_only else KeywordOnly.FORCE
1515+
else:
1516+
kwo = KeywordOnly.NO
1517+
15061518
props = ClassProps(
15071519
is_exception=is_exc,
15081520
is_frozen=is_frozen,
@@ -1524,8 +1536,7 @@ def wrap(cls):
15241536
),
15251537
hash=hashability,
15261538
match_args=match_args,
1527-
is_kw_only=kw_only,
1528-
force_kw_only=force_kw_only,
1539+
kw_only=kwo,
15291540
has_weakref_slot=weakref_slot,
15301541
cache_hash=cache_hash,
15311542
str=str,
@@ -1562,9 +1573,9 @@ def wrap(cls):
15621573
if not frozen:
15631574
builder.add_setattr()
15641575

1565-
if props.hash is _Hashability.HASHABLE:
1576+
if props.hash is Hashability.HASHABLE:
15661577
builder.add_hash()
1567-
elif props.hash is _Hashability.UNHASHABLE:
1578+
elif props.hash is Hashability.UNHASHABLE:
15681579
builder.make_unhashable()
15691580

15701581
if props.init:

tests/test_make.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@
2828
Attribute,
2929
ClassProps,
3030
Factory,
31+
Hashability,
32+
KeywordOnly,
3133
_AndValidator,
3234
_Attributes,
3335
_ClassBuilder,
3436
_CountingAttr,
3537
_determine_attrib_eq_order,
3638
_determine_attrs_eq_order,
3739
_determine_whether_to_implement,
38-
_Hashability,
3940
_transform_attrs,
4041
and_,
4142
fields,
@@ -183,7 +184,7 @@ def test_no_modifications(self):
183184
Does not attach __attrs_attrs__ to the class.
184185
"""
185186
C = make_tc()
186-
_transform_attrs(C, None, False, False, False, True, None)
187+
_transform_attrs(C, None, False, KeywordOnly.NO, True, None)
187188

188189
assert None is getattr(C, "__attrs_attrs__", None)
189190

@@ -193,7 +194,7 @@ def test_normal(self):
193194
"""
194195
C = make_tc()
195196
attrs, _, _ = _transform_attrs(
196-
C, None, False, False, False, True, None
197+
C, None, False, KeywordOnly.NO, True, None
197198
)
198199

199200
assert ["z", "y", "x"] == [a.name for a in attrs]
@@ -208,7 +209,7 @@ class C:
208209
pass
209210

210211
assert _Attributes((), [], {}) == _transform_attrs(
211-
C, None, False, False, False, True, None
212+
C, None, False, KeywordOnly.NO, True, None
212213
)
213214

214215
def test_transforms_to_attribute(self):
@@ -217,7 +218,7 @@ def test_transforms_to_attribute(self):
217218
"""
218219
C = make_tc()
219220
attrs, base_attrs, _ = _transform_attrs(
220-
C, None, False, False, False, True, None
221+
C, None, False, KeywordOnly.NO, True, None
221222
)
222223

223224
assert [] == base_attrs
@@ -235,7 +236,7 @@ class C:
235236
y = attr.ib()
236237

237238
with pytest.raises(ValueError) as e:
238-
_transform_attrs(C, None, False, False, False, True, None)
239+
_transform_attrs(C, None, False, KeywordOnly.NO, True, None)
239240
assert (
240241
"No mandatory attributes allowed after an attribute with a "
241242
"default value or factory. Attribute in question: Attribute"
@@ -264,7 +265,7 @@ class C(B):
264265
y = attr.ib()
265266

266267
attrs, base_attrs, _ = _transform_attrs(
267-
C, None, False, True, False, True, None
268+
C, None, False, KeywordOnly.YES, True, None
268269
)
269270

270271
assert len(attrs) == 3
@@ -280,8 +281,7 @@ class C(B):
280281
C,
281282
None,
282283
False,
283-
True,
284-
True, # force kw-only
284+
KeywordOnly.FORCE,
285285
True,
286286
None,
287287
)
@@ -307,7 +307,7 @@ class C(Base):
307307
y = attr.ib()
308308

309309
attrs, base_attrs, _ = _transform_attrs(
310-
C, {"x": attr.ib()}, False, False, False, True, None
310+
C, {"x": attr.ib()}, False, KeywordOnly.NO, True, None
311311
)
312312

313313
assert [] == base_attrs
@@ -540,10 +540,9 @@ class C:
540540
repr=True,
541541
eq=True,
542542
order=True,
543-
hash=_Hashability.HASHABLE,
543+
hash=Hashability.HASHABLE,
544544
match_args=False,
545-
is_kw_only=True,
546-
force_kw_only=True,
545+
kw_only=KeywordOnly.FORCE,
547546
has_weakref_slot=True,
548547
collect_by_mro=False,
549548
cache_hash=True,
@@ -574,10 +573,9 @@ class CDef:
574573
repr=True,
575574
eq=True,
576575
order=True,
577-
hash=_Hashability.UNHASHABLE,
576+
hash=Hashability.UNHASHABLE,
578577
match_args=True,
579-
is_kw_only=False,
580-
force_kw_only=True,
578+
kw_only=KeywordOnly.NO,
581579
has_weakref_slot=True,
582580
collect_by_mro=False,
583581
cache_hash=False,
@@ -2022,8 +2020,7 @@ class C:
20222020
order=False,
20232021
hash=False,
20242022
match_args=True,
2025-
is_kw_only=False,
2026-
force_kw_only=False,
2023+
kw_only=KeywordOnly.NO,
20272024
has_weakref_slot=False,
20282025
collect_by_mro=True,
20292026
cache_hash=False,
@@ -2059,8 +2056,7 @@ class C:
20592056
order=False,
20602057
hash=False,
20612058
match_args=True,
2062-
is_kw_only=False,
2063-
force_kw_only=False,
2059+
kw_only=KeywordOnly.NO,
20642060
has_weakref_slot=False,
20652061
collect_by_mro=True,
20662062
cache_hash=False,
@@ -2162,8 +2158,7 @@ def our_hasattr(obj, name, /) -> bool:
21622158
order=False,
21632159
hash=False,
21642160
match_args=True,
2165-
is_kw_only=False,
2166-
force_kw_only=False,
2161+
kw_only=KeywordOnly.NO,
21672162
has_weakref_slot=True,
21682163
collect_by_mro=True,
21692164
cache_hash=False,

tests/test_next_gen.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import attrs
1616

1717
from attr._compat import PY_3_11_PLUS
18-
from attr._make import ClassProps, _Hashability
18+
from attr._make import ClassProps, Hashability, KeywordOnly
1919

2020

2121
@attrs.define
@@ -480,13 +480,12 @@ class C:
480480
is_exception=False,
481481
is_slotted=False,
482482
is_frozen=True,
483-
is_kw_only=True,
484-
force_kw_only=False,
483+
kw_only=KeywordOnly.YES,
485484
init=True,
486485
repr=True,
487486
eq=True,
488487
order=True,
489-
hash=_Hashability.HASHABLE,
488+
hash=Hashability.HASHABLE,
490489
match_args=False,
491490
has_weakref_slot=True,
492491
collect_by_mro=True,
@@ -517,10 +516,9 @@ class C:
517516
repr=True,
518517
eq=True,
519518
order=False,
520-
hash=_Hashability.HASHABLE, # b/c frozen
519+
hash=Hashability.HASHABLE, # b/c frozen
521520
match_args=True,
522-
is_kw_only=False,
523-
force_kw_only=False,
521+
kw_only=KeywordOnly.NO,
524522
has_weakref_slot=True,
525523
collect_by_mro=True,
526524
cache_hash=False,

0 commit comments

Comments
 (0)