From 0411e262fb0e6b879efe00e1663791078eff2e30 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 15 Mar 2025 15:48:25 -0400 Subject: [PATCH 1/4] Add equality checks to Domains This allows easier introspection of API responses. Very useful for unit-testing behaviour of wrappers of hcloud. --- hcloud/core/domain.py | 11 +++++++++++ tests/unit/core/test_domain.py | 7 +++++++ 2 files changed, 18 insertions(+) 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_domain.py b/tests/unit/core/test_domain.py index d5e588f1..9811b450 100644 --- a/tests/unit/core/test_domain.py +++ b/tests/unit/core/test_domain.py @@ -156,3 +156,10 @@ 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") From 2226a66d7b262f0d9ab00b241bdd16a59ab45f49 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 17 Mar 2025 18:53:57 -0400 Subject: [PATCH 2/4] Implement __eq__ on BoundModelBase --- hcloud/core/client.py | 6 ++++++ tests/unit/core/test_client.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) 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/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() From b60af4d206668217ea9451eec0b74402675f4b8d Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Mon, 17 Mar 2025 19:45:06 -0400 Subject: [PATCH 3/4] Nested equality check --- tests/unit/core/test_domain.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unit/core/test_domain.py b/tests/unit/core/test_domain.py index 9811b450..6d2ad9c6 100644 --- a/tests/unit/core/test_domain.py +++ b/tests/unit/core/test_domain.py @@ -163,3 +163,14 @@ def test__eq__(self): 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 From 9e083d6b635e5cda4b5fc0af869f71f3298d249b Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Tue, 18 Mar 2025 14:43:42 -0400 Subject: [PATCH 4/4] Add a test with a nested list --- tests/unit/core/test_domain.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unit/core/test_domain.py b/tests/unit/core/test_domain.py index 6d2ad9c6..c449df4e 100644 --- a/tests/unit/core/test_domain.py +++ b/tests/unit/core/test_domain.py @@ -174,3 +174,14 @@ def test_nested__eq__(self): 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