Skip to content

Commit 4d430f6

Browse files
authored
feat: add equality checks to domains (#481)
This allows easier introspection of API responses. Very useful for unit-testing behaviour of wrappers of hcloud.
1 parent 125a2ba commit 4d430f6

File tree

4 files changed

+63
-0
lines changed

4 files changed

+63
-0
lines changed

hcloud/core/client.py

+6
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,9 @@ def __repr__(self) -> str:
9696
# models, as they will generate a lot of API call trying to print all the fields
9797
# of the model.
9898
return object.__repr__(self)
99+
100+
def __eq__(self, other: Any) -> bool:
101+
"""Compare a bound model object with another of the same type."""
102+
if not isinstance(other, self.__class__):
103+
return NotImplemented
104+
return self.data_model == other.data_model

hcloud/core/domain.py

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from typing import Any
4+
35

46
class BaseDomain:
57
__api_properties__: tuple
@@ -16,6 +18,15 @@ def __repr__(self) -> str:
1618
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__] # type: ignore[var-annotated]
1719
return f"{self.__class__.__qualname__}({', '.join(kwargs)})"
1820

21+
def __eq__(self, other: Any) -> bool:
22+
"""Compare a domain object with another of the same type."""
23+
if not isinstance(other, self.__class__):
24+
return NotImplemented
25+
for key in self.__api_properties__:
26+
if getattr(self, key) != getattr(other, key):
27+
return False
28+
return True
29+
1930

2031
class DomainIdentityMixin:
2132

tests/unit/core/test_client.py

+17
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ def test_get_non_exists_model_attribute_incomplete_model(
8181
client.get_by_id.assert_not_called()
8282
assert bound_model.complete is False
8383

84+
def test_equality(self, bound_model_class, client):
85+
data = {"id": 1, "name": "name", "description": "my_description"}
86+
bound_model_a = bound_model_class(client=client, data=data)
87+
bound_model_b = bound_model_class(client=client, data=data)
88+
89+
# Comparing a bound model with a base domain
90+
assert bound_model_a == bound_model_a.data_model
91+
92+
# Identical bound models
93+
assert bound_model_a == bound_model_b
94+
assert bound_model_a == bound_model_b.data_model
95+
96+
# Differing bound models
97+
bound_model_b.data_model.name = "changed_name"
98+
assert bound_model_a != bound_model_b
99+
assert bound_model_a != bound_model_b.data_model
100+
84101

85102
class TestClientEntityBase:
86103
@pytest.fixture()

tests/unit/core/test_domain.py

+29
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,32 @@ def test_from_dict_ok(self, data_dict, expected_result):
156156
)
157157
def test_repr_ok(self, data, expected):
158158
assert data.__repr__() == expected
159+
160+
def test__eq__(self):
161+
a1 = ActionDomain(id=1, name="action")
162+
assert a1 == ActionDomain(id=1, name="action")
163+
assert a1 != ActionDomain(id=2, name="action")
164+
assert a1 != ActionDomain(id=1, name="something")
165+
assert a1 != SomeOtherDomain(id=1, name="action")
166+
167+
def test_nested__eq__(self):
168+
child1 = ActionDomain(id=1, name="child")
169+
d1 = SomeOtherDomain(id=1, name="parent", child=child1)
170+
d2 = SomeOtherDomain(id=1, name="parent", child=child1)
171+
172+
assert d1 == d2
173+
174+
d2.child = ActionDomain(id=2, name="child2")
175+
176+
assert d1 != d2
177+
178+
def test_nested_list__eq__(self):
179+
child1 = ActionDomain(id=1, name="child")
180+
d1 = SomeOtherDomain(id=1, name="parent", child=[child1])
181+
d2 = SomeOtherDomain(id=1, name="parent", child=[child1])
182+
183+
assert d1 == d2
184+
185+
d2.child = [ActionDomain(id=2, name="child2")]
186+
187+
assert d1 != d2

0 commit comments

Comments
 (0)