diff --git a/hcloud/core/client.py b/hcloud/core/client.py index d213daf0..0b86c837 100644 --- a/hcloud/core/client.py +++ b/hcloud/core/client.py @@ -96,3 +96,9 @@ def __repr__(self) -> str: # models, as they will generate a lot of API call trying to print all the fields # of the model. return object.__repr__(self) + + def __eq__(self, other: Any) -> bool: + """Compare a bound model object with another of the same type.""" + if not isinstance(other, self.__class__): + return NotImplemented + return self.data_model == other.data_model diff --git a/hcloud/core/domain.py b/hcloud/core/domain.py index d8485257..fa4b4bb9 100644 --- a/hcloud/core/domain.py +++ b/hcloud/core/domain.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + class BaseDomain: __api_properties__: tuple @@ -16,6 +18,15 @@ def __repr__(self) -> str: kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__] # type: ignore[var-annotated] return f"{self.__class__.__qualname__}({', '.join(kwargs)})" + def __eq__(self, other: Any) -> bool: + """Compare a domain object with another of the same type.""" + if not isinstance(other, self.__class__): + return NotImplemented + for key in self.__api_properties__: + if getattr(self, key) != getattr(other, key): + return False + return True + class DomainIdentityMixin: diff --git a/tests/unit/core/test_client.py b/tests/unit/core/test_client.py index 3155eed6..4a6542eb 100644 --- a/tests/unit/core/test_client.py +++ b/tests/unit/core/test_client.py @@ -81,6 +81,23 @@ def test_get_non_exists_model_attribute_incomplete_model( client.get_by_id.assert_not_called() assert bound_model.complete is False + def test_equality(self, bound_model_class, client): + data = {"id": 1, "name": "name", "description": "my_description"} + bound_model_a = bound_model_class(client=client, data=data) + bound_model_b = bound_model_class(client=client, data=data) + + # Comparing a bound model with a base domain + assert bound_model_a == bound_model_a.data_model + + # Identical bound models + assert bound_model_a == bound_model_b + assert bound_model_a == bound_model_b.data_model + + # Differing bound models + bound_model_b.data_model.name = "changed_name" + assert bound_model_a != bound_model_b + assert bound_model_a != bound_model_b.data_model + class TestClientEntityBase: @pytest.fixture() diff --git a/tests/unit/core/test_domain.py b/tests/unit/core/test_domain.py index d5e588f1..c449df4e 100644 --- a/tests/unit/core/test_domain.py +++ b/tests/unit/core/test_domain.py @@ -156,3 +156,32 @@ def test_from_dict_ok(self, data_dict, expected_result): ) def test_repr_ok(self, data, expected): assert data.__repr__() == expected + + def test__eq__(self): + a1 = ActionDomain(id=1, name="action") + assert a1 == ActionDomain(id=1, name="action") + assert a1 != ActionDomain(id=2, name="action") + assert a1 != ActionDomain(id=1, name="something") + assert a1 != SomeOtherDomain(id=1, name="action") + + def test_nested__eq__(self): + child1 = ActionDomain(id=1, name="child") + d1 = SomeOtherDomain(id=1, name="parent", child=child1) + d2 = SomeOtherDomain(id=1, name="parent", child=child1) + + assert d1 == d2 + + d2.child = ActionDomain(id=2, name="child2") + + assert d1 != d2 + + def test_nested_list__eq__(self): + child1 = ActionDomain(id=1, name="child") + d1 = SomeOtherDomain(id=1, name="parent", child=[child1]) + d2 = SomeOtherDomain(id=1, name="parent", child=[child1]) + + assert d1 == d2 + + d2.child = [ActionDomain(id=2, name="child2")] + + assert d1 != d2