Skip to content

Commit

Permalink
Improve the hashability of exceptions when they contain hashable data.
Browse files Browse the repository at this point in the history
  • Loading branch information
Julian committed Jul 18, 2023
1 parent 1c325d7 commit ea0b952
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 9 deletions.
6 changes: 6 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Changelog
=========

v0.29.2
-------

* Improve the hashability of exceptions when they contain hashable data.


v0.29.1
-------

Expand Down
27 changes: 26 additions & 1 deletion referencing/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ def __eq__(self, other: Any) -> bool:
return NotImplemented
return attrs.astuple(self) == attrs.astuple(other)

def __hash__(self) -> int:
return hash(attrs.astuple(self))


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

def __hash__(self) -> int:
return hash(attrs.astuple(self))


@frozen
class Unretrievable(KeyError):
Expand All @@ -58,6 +64,14 @@ class Unretrievable(KeyError):

ref: URI

def __eq__(self, other: Any) -> bool:
if self.__class__ is not other.__class__:
return NotImplemented
return attrs.astuple(self) == attrs.astuple(other)

def __hash__(self) -> int:
return hash(attrs.astuple(self))


@frozen
class CannotDetermineSpecification(Exception):
Expand All @@ -70,8 +84,16 @@ class CannotDetermineSpecification(Exception):

contents: Any

def __eq__(self, other: Any) -> bool:
if self.__class__ is not other.__class__:
return NotImplemented
return attrs.astuple(self) == attrs.astuple(other)

@attrs.frozen
def __hash__(self) -> int:
return hash(attrs.astuple(self))


@attrs.frozen # Because here we allow subclassing below.
class Unresolvable(Exception):
"""
A reference was unresolvable.
Expand All @@ -84,6 +106,9 @@ def __eq__(self, other: Any) -> bool:
return NotImplemented
return attrs.astuple(self) == attrs.astuple(other)

def __hash__(self) -> int:
return hash(attrs.astuple(self))


@frozen
class PointerToNowhere(Unresolvable):
Expand Down
29 changes: 21 additions & 8 deletions referencing/tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@
from referencing import Resource, exceptions


def pairs(*choices):
def pairs(choices):
return itertools.combinations(choices, 2)


@pytest.mark.parametrize(
"one, two",
pairs(
exceptions.NoSuchResource("urn:example:foo"),
exceptions.NoInternalID(Resource.opaque({})),
exceptions.Unresolvable("urn:example:foo"),
),
TRUE = Resource.opaque(True)


thunks = (
lambda: exceptions.CannotDetermineSpecification(TRUE),
lambda: exceptions.NoSuchResource("urn:example:foo"),
lambda: exceptions.NoInternalID(TRUE),
lambda: exceptions.InvalidAnchor(resource=TRUE, anchor="foo", ref="a#b"),
lambda: exceptions.NoSuchAnchor(resource=TRUE, anchor="foo", ref="a#b"),
lambda: exceptions.PointerToNowhere(resource=TRUE, ref="urn:example:foo"),
lambda: exceptions.Unresolvable("urn:example:foo"),
lambda: exceptions.Unretrievable("urn:example:foo"),
)


@pytest.mark.parametrize("one, two", pairs(each() for each in thunks))
def test_eq_incompatible_types(one, two):
assert one != two


@pytest.mark.parametrize("thunk", thunks)
def test_hash(thunk):
assert thunk() in {thunk()}

0 comments on commit ea0b952

Please sign in to comment.