Skip to content

Commit

Permalink
Make auto_exc=True classes hashable by ID
Browse files Browse the repository at this point in the history
They now behave as the docs claim.

Fixes #543
  • Loading branch information
hynek committed Aug 18, 2019
1 parent 8174b03 commit 7c92cf8
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 18 deletions.
1 change: 1 addition & 0 deletions changelog.d/543.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``@attr.s(auto_exc=True)`` now generates classes that are hashable by ID, as the documentation always claimed it would.
11 changes: 5 additions & 6 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,20 +912,19 @@ def wrap(cls):
raise TypeError(
"Invalid value for hash. Must be True, False, or None."
)
elif hash is False or (hash is None and cmp is False):
elif hash is False or (hash is None and cmp is False) or is_exc:
# Hash by ID aka NOP.
if cache_hash:
raise TypeError(
"Invalid value for cache_hash. To use hash caching,"
" hashing must be either explicitly or implicitly "
"enabled."
)
elif (
hash is True
or (hash is None and cmp is True and frozen is True)
and is_exc is False
):
elif hash is True or (hash is None and cmp is True and frozen is True):
# Build a __hash__ if told so, or if it's safe.
builder.add_hash()
else:
# Raise TypeError on attempts to hash.
if cache_hash:
raise TypeError(
"Invalid value for cache_hash. To use hash caching,"
Expand Down
30 changes: 18 additions & 12 deletions tests/test_dark_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,8 @@ class C(object):
@pytest.mark.parametrize("frozen", [True, False])
def test_auto_exc(self, slots, frozen):
"""
Classes with auto_exc=True have a Exception-style __str__, are neither
comparable nor hashable, and store the fields additionally in
self.args.
Classes with auto_exc=True have a Exception-style __str__, compare and
hash by id, and store the fields additionally in self.args.
"""

@attr.s(auto_exc=True, slots=slots, frozen=frozen)
Expand Down Expand Up @@ -545,21 +544,28 @@ class FooError(Exception):
assert FooErrorMade(1, "foo") != FooErrorMade(1, "foo")

for cls in (FooError, FooErrorMade):
with pytest.raises(cls) as ei:
with pytest.raises(cls) as ei1:
raise cls(1, "foo")

e = ei.value
with pytest.raises(cls) as ei2:
raise cls(1, "foo")

e1 = ei1.value
e2 = ei2.value

assert e is e
assert e == e
assert "(1, 'foo')" == str(e)
assert (1, "foo") == e.args
assert e1 is e1
assert e1 == e1
assert e2 == e2
assert e1 != e2
assert "(1, 'foo')" == str(e1) == str(e2)
assert (1, "foo") == e1.args == e2.args

with pytest.raises(TypeError):
hash(e)
hash(e1) == hash(e1)
hash(e2) == hash(e2)

if not frozen:
deepcopy(e)
deepcopy(e1)
deepcopy(e2)

@pytest.mark.parametrize("slots", [True, False])
@pytest.mark.parametrize("frozen", [True, False])
Expand Down

0 comments on commit 7c92cf8

Please sign in to comment.