Skip to content

Commit ea0b952

Browse files
committed
Improve the hashability of exceptions when they contain hashable data.
Refs: python-jsonschema/jsonschema#1126
1 parent 1c325d7 commit ea0b952

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

docs/changes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
Changelog
33
=========
44

5+
v0.29.2
6+
-------
7+
8+
* Improve the hashability of exceptions when they contain hashable data.
9+
10+
511
v0.29.1
612
-------
713

referencing/exceptions.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ def __eq__(self, other: Any) -> bool:
3030
return NotImplemented
3131
return attrs.astuple(self) == attrs.astuple(other)
3232

33+
def __hash__(self) -> int:
34+
return hash(attrs.astuple(self))
35+
3336

3437
@frozen
3538
class NoInternalID(Exception):
@@ -49,6 +52,9 @@ def __eq__(self, other: Any) -> bool:
4952
return NotImplemented
5053
return attrs.astuple(self) == attrs.astuple(other)
5154

55+
def __hash__(self) -> int:
56+
return hash(attrs.astuple(self))
57+
5258

5359
@frozen
5460
class Unretrievable(KeyError):
@@ -58,6 +64,14 @@ class Unretrievable(KeyError):
5864

5965
ref: URI
6066

67+
def __eq__(self, other: Any) -> bool:
68+
if self.__class__ is not other.__class__:
69+
return NotImplemented
70+
return attrs.astuple(self) == attrs.astuple(other)
71+
72+
def __hash__(self) -> int:
73+
return hash(attrs.astuple(self))
74+
6175

6276
@frozen
6377
class CannotDetermineSpecification(Exception):
@@ -70,8 +84,16 @@ class CannotDetermineSpecification(Exception):
7084

7185
contents: Any
7286

87+
def __eq__(self, other: Any) -> bool:
88+
if self.__class__ is not other.__class__:
89+
return NotImplemented
90+
return attrs.astuple(self) == attrs.astuple(other)
7391

74-
@attrs.frozen
92+
def __hash__(self) -> int:
93+
return hash(attrs.astuple(self))
94+
95+
96+
@attrs.frozen # Because here we allow subclassing below.
7597
class Unresolvable(Exception):
7698
"""
7799
A reference was unresolvable.
@@ -84,6 +106,9 @@ def __eq__(self, other: Any) -> bool:
84106
return NotImplemented
85107
return attrs.astuple(self) == attrs.astuple(other)
86108

109+
def __hash__(self) -> int:
110+
return hash(attrs.astuple(self))
111+
87112

88113
@frozen
89114
class PointerToNowhere(Unresolvable):

referencing/tests/test_exceptions.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,30 @@
55
from referencing import Resource, exceptions
66

77

8-
def pairs(*choices):
8+
def pairs(choices):
99
return itertools.combinations(choices, 2)
1010

1111

12-
@pytest.mark.parametrize(
13-
"one, two",
14-
pairs(
15-
exceptions.NoSuchResource("urn:example:foo"),
16-
exceptions.NoInternalID(Resource.opaque({})),
17-
exceptions.Unresolvable("urn:example:foo"),
18-
),
12+
TRUE = Resource.opaque(True)
13+
14+
15+
thunks = (
16+
lambda: exceptions.CannotDetermineSpecification(TRUE),
17+
lambda: exceptions.NoSuchResource("urn:example:foo"),
18+
lambda: exceptions.NoInternalID(TRUE),
19+
lambda: exceptions.InvalidAnchor(resource=TRUE, anchor="foo", ref="a#b"),
20+
lambda: exceptions.NoSuchAnchor(resource=TRUE, anchor="foo", ref="a#b"),
21+
lambda: exceptions.PointerToNowhere(resource=TRUE, ref="urn:example:foo"),
22+
lambda: exceptions.Unresolvable("urn:example:foo"),
23+
lambda: exceptions.Unretrievable("urn:example:foo"),
1924
)
25+
26+
27+
@pytest.mark.parametrize("one, two", pairs(each() for each in thunks))
2028
def test_eq_incompatible_types(one, two):
2129
assert one != two
30+
31+
32+
@pytest.mark.parametrize("thunk", thunks)
33+
def test_hash(thunk):
34+
assert thunk() in {thunk()}

0 commit comments

Comments
 (0)