From 2091aa0e5619e41bc50940532144aca1850e378c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <pmaslanka@smok.co>
Date: Sat, 9 Oct 2021 17:35:42 +0200
Subject: [PATCH] v2.17.23 fixed OmniHashableMixin

---
 CHANGELOG.md                                 |  2 +
 satella/__init__.py                          |  2 +-
 satella/coding/structures/mixins/hashable.py | 41 +++++++++++++-------
 tests/test_coding/test_structures.py         | 27 +++++++++++++
 4 files changed, 57 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e7d3a83..d0e3fe69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
 # v2.17.23
 
 * un-deprecated the `cps` metric
+* OmniHashableMixin will check for typing as well
+* fixed a bug in OmniHashableMixin regarding attribute checks
diff --git a/satella/__init__.py b/satella/__init__.py
index 90911dcd..300d2842 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.17.23a1'
+__version__ = '2.17.23'
diff --git a/satella/coding/structures/mixins/hashable.py b/satella/coding/structures/mixins/hashable.py
index 930ca386..b53be462 100644
--- a/satella/coding/structures/mixins/hashable.py
+++ b/satella/coding/structures/mixins/hashable.py
@@ -143,6 +143,8 @@ class OmniHashableMixin(metaclass=ABCMeta):
     Do everything in your power to make specified fields immutable, as mutating them will result
     in a different hash.
 
+    This will also check if the other value is an instance of this instance's class.
+
     _HASH_FIELDS_TO_USE can be also a single string, in this case a single field called by this
     name will be taken.
 
@@ -178,29 +180,40 @@ class OmniHashableMixin(metaclass=ABCMeta):
         """
         Note that this will only compare _HASH_FIELDS_TO_USE
         """
+        if not isinstance(other, type(self)):
+            return False
+
         if isinstance(other, OmniHashableMixin):
             cmpr_by = self._HASH_FIELDS_TO_USE
-
-            if isinstance(cmpr_by, str):
-                return getattr(self, cmpr_by) == getattr(other, cmpr_by)
-
-            for field_name in self._HASH_FIELDS_TO_USE:
-                if getattr(self, field_name) != getattr(other, field_name):
-                    return False
-            return True
+            try:
+                if isinstance(cmpr_by, str):
+                    return getattr(self, cmpr_by) == getattr(other, cmpr_by)
+
+                for field_name in self._HASH_FIELDS_TO_USE:
+                    if getattr(self, field_name) != getattr(other, field_name):
+                        return False
+                return True
+            except AttributeError:
+                return False
         else:
             return super().__eq__(other)
 
     def __ne__(self, other) -> bool:
+        if not isinstance(other, type(self)):
+            return True
+
         if isinstance(other, OmniHashableMixin):
             cmpr_by = self._HASH_FIELDS_TO_USE
 
-            if isinstance(cmpr_by, str):
-                return getattr(self, cmpr_by) != getattr(other, cmpr_by)
+            try:
+                if isinstance(cmpr_by, str):
+                    return getattr(self, cmpr_by) != getattr(other, cmpr_by)
 
-            for field_name in cmpr_by:
-                if getattr(self, field_name) != getattr(other, field_name):
-                    return True
-            return False
+                for field_name in cmpr_by:
+                    if getattr(self, field_name) != getattr(other, field_name):
+                        return True
+                return False
+            except AttributeError:
+                return True
         else:
             return super().__ne__(other)
diff --git a/tests/test_coding/test_structures.py b/tests/test_coding/test_structures.py
index c951204f..b0fd4a7f 100644
--- a/tests/test_coding/test_structures.py
+++ b/tests/test_coding/test_structures.py
@@ -664,6 +664,33 @@ class TestMisc(unittest.TestCase):
         self.assertEqual(a[e1], '1')
         self.assertEqual(hash(e1), hash(2))
 
+    def test_omni_not_filled_in_fields(self):
+        class Omni1(OmniHashableMixin):
+            _HASH_FIELDS_TO_USE = 'a'
+
+            def __init__(self, a=None):
+                if a is not None:
+                    self.a = a
+
+        self.assertNotEqual(Omni1(3), Omni1())
+        self.assertFalse(Omni1() == Omni1(3))
+
+    def test_omni_different_hierarchies(self):
+        class Omni1(OmniHashableMixin):
+            _HASH_FIELDS_TO_USE = 'a'
+
+            def __init__(self, a):
+                self.a = a
+
+        class Omni2(OmniHashableMixin):
+            _HASH_FIELDS_TO_USE = 'a'
+
+            def __init__(self, a):
+                self.a = a
+
+        self.assertNotEqual(Omni1(1), Omni2(1))
+        self.assertFalse(Omni1(1) == Omni2(1))
+
     def test_omni_single_field(self):
         class Omni(OmniHashableMixin):
             _HASH_FIELDS_TO_USE = 'a'
-- 
GitLab