Skip to content

Commit

Permalink
Make auto_exc=True classes hashable by ID (#563)
Browse files Browse the repository at this point in the history
* Make auto_exc=True classes hashable by ID

They now behave as the docs claim.

Fixes #543

* Add PR newsfragment

* Be more specific about NOP
  • Loading branch information
hynek committed Aug 19, 2019
1 parent 8174b03 commit bde0801
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 18 deletions.
1 change: 1 addition & 0 deletions changelog.d/543.change.rst
@@ -0,0 +1 @@
``@attr.s(auto_exc=True)`` now generates classes that are hashable by ID, as the documentation always claimed it would.
1 change: 1 addition & 0 deletions changelog.d/563.change.rst
@@ -0,0 +1 @@
``@attr.s(auto_exc=True)`` now generates classes that are hashable by ID, as the documentation always claimed it would.
12 changes: 6 additions & 6 deletions src/attr/_make.py
Expand Up @@ -912,20 +912,20 @@ 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:
# Don't do anything. Should fall back to __object__'s __hash__
# which is by id.
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
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 bde0801

Please sign in to comment.