From 7ce376f6f0c6ab37182151bed956648501dbe294 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <pmaslanka@smok.co>
Date: Fri, 29 Oct 2021 15:58:39 +0200
Subject: [PATCH] 2.18.7

---
 CHANGELOG.md                                 |  3 ++
 docs/coding/structures.rst                   | 11 +++++++
 satella/__init__.py                          |  2 +-
 satella/coding/structures/__init__.py        |  7 +++--
 satella/coding/structures/immutable.py       | 18 +++++++++++
 satella/coding/structures/mixins/__init__.py |  3 +-
 satella/coding/structures/mixins/eqable.py   | 26 +++++++++++++++
 tests/test_coding/test_structures.py         | 33 ++++++++++++++++++--
 8 files changed, 96 insertions(+), 7 deletions(-)
 create mode 100644 satella/coding/structures/mixins/eqable.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7035496a..b57f49c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1 +1,4 @@
 # v2.18.7
+
+* added NotEqualToAnything
+* added DictionaryEQAble
diff --git a/docs/coding/structures.rst b/docs/coding/structures.rst
index 79b679ff..611a2d7c 100644
--- a/docs/coding/structures.rst
+++ b/docs/coding/structures.rst
@@ -2,6 +2,17 @@
 Structures
 ==========
 
+.. autoclass:: satella.coding.structures.NotEqualToAnything
+    :members:
+
+You can also use the following singleton.
+
+.. code-block:: python
+    from satella.coding.structures import NOT_EQUAL_TO_ANYTHING
+
+    assert NOT_EQUAL_TO_ANYTHING != NOT_EQUAL_TO_ANYTHING
+
+
 .. autoclass:: satella.coding.structures.Vector
     :members:
 
diff --git a/satella/__init__.py b/satella/__init__.py
index 7c95a3b1..fd0aaca7 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.18.7a1'
+__version__ = '2.18.7'
diff --git a/satella/coding/structures/__init__.py b/satella/coding/structures/__init__.py
index 95932389..142f5939 100644
--- a/satella/coding/structures/__init__.py
+++ b/satella/coding/structures/__init__.py
@@ -3,10 +3,10 @@ from .dictionaries import DictObject, apply_dict_object, DictionaryView, TwoWayD
     CacheDict, ExclusiveWritebackCache, CountingDict, LRUCacheDict, DefaultDict
 from .hashable_objects import HashableWrapper
 from .heaps import Heap, SetHeap, TimeBasedHeap, TimeBasedSetHeap
-from .immutable import Immutable, frozendict
+from .immutable import Immutable, frozendict, NotEqualToAnything, NOT_EQUAL_TO_ANYTHING
 from .mixins import OmniHashableMixin, ReprableMixin, StrEqHashableMixin, ComparableIntEnum, \
     HashableIntEnum, ComparableAndHashableBy, ComparableAndHashableByInt, ComparableEnum, \
-    HashableMixin, ComparableAndHashableByStr
+    HashableMixin, ComparableAndHashableByStr, DictionaryEQAble
 from .proxy import Proxy
 from .queues import Subqueue
 from .ranking import Ranking
@@ -26,8 +26,9 @@ __all__ = [
     'DBStorage', 'SyncableDroppable',
     'LRU',
     'LRUCacheDict',
+    'NotEqualToAnything', 'NOT_EQUAL_TO_ANYTHING',
     'HashableMixin',
-    'CountingDict',
+    'CountingDict', 'DictionaryEQAble',
     'Subqueue',
     'ExclusiveWritebackCache',
     'DefaultDict',
diff --git a/satella/coding/structures/immutable.py b/satella/coding/structures/immutable.py
index ec62ddf4..12385531 100644
--- a/satella/coding/structures/immutable.py
+++ b/satella/coding/structures/immutable.py
@@ -46,6 +46,24 @@ class Immutable(metaclass=ImmutableMetaType):
             super().__delattr__(attr)
 
 
+class NotEqualToAnything:
+    """
+    A class that defines __eq__ and __ne__ to be always False and True respectively
+
+    When exploiting this class please make sure that it gets to be the first
+    operand in the comparison, so that it's __eq__ and __ne__ gets called!
+    """
+
+    def __eq__(self, other) -> bool:
+        return False
+
+    def __next__(self, other) -> bool:
+        return True
+
+
+NOT_EQUAL_TO_ANYTHING = NotEqualToAnything()
+
+
 class frozendict(dict):
     """
     A hashable dict with express forbid to change it's values
diff --git a/satella/coding/structures/mixins/__init__.py b/satella/coding/structures/mixins/__init__.py
index 85e9f77b..948d8631 100644
--- a/satella/coding/structures/mixins/__init__.py
+++ b/satella/coding/structures/mixins/__init__.py
@@ -2,8 +2,9 @@ from .enums import ComparableEnum, ComparableIntEnum, HashableIntEnum
 from .hashable import ComparableAndHashableBy, ComparableAndHashableByInt, \
     OmniHashableMixin, HashableMixin, ComparableAndHashableByStr
 from .strings import ReprableMixin, StrEqHashableMixin
+from .eqable import DictionaryEQAble
 
 __all__ = ['ComparableIntEnum', 'ComparableEnum', 'ComparableAndHashableBy',
            'HashableIntEnum', 'ComparableAndHashableByInt', 'OmniHashableMixin',
            'ReprableMixin', 'StrEqHashableMixin', 'HashableMixin',
-           'ComparableAndHashableByStr']
+           'ComparableAndHashableByStr', 'DictionaryEQAble']
diff --git a/satella/coding/structures/mixins/eqable.py b/satella/coding/structures/mixins/eqable.py
new file mode 100644
index 00000000..ca9a5eda
--- /dev/null
+++ b/satella/coding/structures/mixins/eqable.py
@@ -0,0 +1,26 @@
+from ..immutable import NOT_EQUAL_TO_ANYTHING
+
+
+class DictionaryEQAble:
+    """
+    A class mix-in that defines __eq__ and __ne__ to be:
+
+    - both the same exact type (so subclassing won't work)
+    - have the exact same __dict__
+    """
+
+    def __eq__(self, other) -> bool:
+        if type(self) != type(other):
+            return False
+        for key in self.__dict__.keys():
+            if getattr(other, key, NOT_EQUAL_TO_ANYTHING) != self.__dict__[key]:
+                return False
+        return True
+
+    def __ne__(self, other) -> bool:
+        if type(self) != type(other):
+            return True
+        for key in self.__dict__.keys():
+            if getattr(other, key, NOT_EQUAL_TO_ANYTHING) != self.__dict__[key]:
+                return True
+        return True
diff --git a/tests/test_coding/test_structures.py b/tests/test_coding/test_structures.py
index b0fd4a7f..4e6a6fe1 100644
--- a/tests/test_coding/test_structures.py
+++ b/tests/test_coding/test_structures.py
@@ -15,10 +15,39 @@ from satella.coding.structures import TimeBasedHeap, Heap, typednamedtuple, \
     CacheDict, StrEqHashableMixin, ComparableIntEnum, HashableIntEnum, ComparableAndHashableBy, \
     ComparableAndHashableByInt, SparseMatrix, ExclusiveWritebackCache, Subqueue, \
     CountingDict, ComparableEnum, LRU, LRUCacheDict, Vector, DefaultDict, PushIterable, \
-    ComparableAndHashableByStr
+    ComparableAndHashableByStr, NotEqualToAnything, NOT_EQUAL_TO_ANYTHING, DictionaryEQAble
 
 
-class TestMisc(unittest.TestCase):
+class TestStructures(unittest.TestCase):
+
+    def test_dictionary_eqable(self):
+        class Dupa(DictionaryEQAble):
+            def __init__(self, a):
+                self.a = a
+
+        class Dupa2(DictionaryEQAble):
+            def __init__(self, a):
+                self.a = a
+
+        a = Dupa(5)
+        b = Dupa(5)
+        c = Dupa(6)
+        d = Dupa2(5)
+        self.assertTrue(a == b)
+        self.assertFalse(a == c)
+        self.assertFalse(a == d)
+
+    def test_not_equal_to_anything(self):
+        self.assertTrue(NOT_EQUAL_TO_ANYTHING != NOT_EQUAL_TO_ANYTHING)
+        self.assertFalse(NOT_EQUAL_TO_ANYTHING == NOT_EQUAL_TO_ANYTHING)
+
+        class Dupa(NotEqualToAnything):
+            pass
+
+        a = Dupa()
+
+        self.assertTrue(a != a)
+        self.assertFalse(a == a)
 
     def test_push_iterable(self):
 
-- 
GitLab