Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit 03dd5e1

Browse files
grktshcguardia
andcommitted
fix: fix missing __ne__ methods (#279)
* fix: fix missing __ne__ methods Removed unnecessary `_datastore_query._Result.__ne__` since `functools.total_ordering` defines it from `__eq__`. Added `key.Key.__ne__` from App Engine NDB for Python 2 compatibility. Added `model._NotEqualMixin` from App Engine NDB and used it in `model.IndexProperty`, `model.Index`, `model.IndexState`, `model._BaseValue` and `model.Model` for Python 2 compatibility. Added `query.Node.__ne__` from App Engine NDB so that it is used by all subclasses and removed unnecessary `query.FilterNode.__ne__`. * Appease Sphinx Co-authored-by: Carlos de la Guardia <cguardia@yahoo.com>
1 parent 04279a4 commit 03dd5e1

8 files changed

Lines changed: 116 additions & 17 deletions

File tree

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
("py:meth", "_datastore_query.Cursor.urlsafe"),
4141
("py:class", "google.cloud.ndb.context._Context"),
4242
("py:class", "google.cloud.ndb.metadata._BaseMetadata"),
43+
("py:class", "google.cloud.ndb.model._NotEqualMixin"),
4344
("py:class", "google.cloud.ndb._options.ReadOptions"),
4445
("py:class", "QueryIterator"),
4546
("py:class", ".."),

google/cloud/ndb/_datastore_query.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -649,10 +649,6 @@ def __eq__(self, other):
649649

650650
return self._compare(other) == 0
651651

652-
def __ne__(self, other):
653-
"""For total ordering. Python 2.7 only."""
654-
return self._compare(other) != 0
655-
656652
def _compare(self, other):
657653
"""Compare this result to another result for sorting.
658654

google/cloud/ndb/key.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,12 @@ def __eq__(self, other):
380380

381381
return self._tuple() == other._tuple()
382382

383+
def __ne__(self, other):
384+
"""The opposite of __eq__."""
385+
if not isinstance(other, Key):
386+
return NotImplemented
387+
return not self.__eq__(other)
388+
383389
def __lt__(self, other):
384390
"""Less than ordering."""
385391
if not isinstance(other, Key):

google/cloud/ndb/model.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,18 @@ class UserNotFoundError(exceptions.Error):
374374
"""No email argument was specified, and no user is logged in."""
375375

376376

377-
class IndexProperty(object):
377+
class _NotEqualMixin(object):
378+
"""Mix-in class that implements __ne__ in terms of __eq__."""
379+
380+
def __ne__(self, other):
381+
"""Implement self != other as not(self == other)."""
382+
eq = self.__eq__(other)
383+
if eq is NotImplemented:
384+
return NotImplemented
385+
return not eq
386+
387+
388+
class IndexProperty(_NotEqualMixin):
378389
"""Immutable object representing a single property in an index."""
379390

380391
__slots__ = ("_name", "_direction")
@@ -412,7 +423,7 @@ def __hash__(self):
412423
return hash((self.name, self.direction))
413424

414425

415-
class Index(object):
426+
class Index(_NotEqualMixin):
416427
"""Immutable object representing an index."""
417428

418429
__slots__ = ("_kind", "_properties", "_ancestor")
@@ -461,7 +472,7 @@ def __hash__(self):
461472
return hash((self.kind, self.properties, self.ancestor))
462473

463474

464-
class IndexState(object):
475+
class IndexState(_NotEqualMixin):
465476
"""Immutable object representing an index and its state."""
466477

467478
__slots__ = ("_definition", "_state", "_id")
@@ -795,7 +806,7 @@ def _fix_up(self, cls, code_name):
795806
"""
796807

797808

798-
class _BaseValue(object):
809+
class _BaseValue(_NotEqualMixin):
799810
"""A marker object wrapping a "base type" value.
800811
801812
This is used to be able to tell whether ``entity._values[name]`` is a
@@ -4295,7 +4306,7 @@ def __repr__(cls):
42954306

42964307

42974308
@six.add_metaclass(MetaModel)
4298-
class Model(object):
4309+
class Model(_NotEqualMixin):
42994310
"""A class describing Cloud Datastore entities.
43004311
43014312
Model instances are usually called entities. All model classes

google/cloud/ndb/query.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,10 @@ def __eq__(self, other):
422422

423423
def __ne__(self, other):
424424
# Python 2.7 requires this method to be implemented.
425-
raise NotImplementedError
425+
eq = self.__eq__(other)
426+
if eq is not NotImplemented:
427+
eq = not eq
428+
return eq
426429

427430
def __le__(self, unused_other):
428431
raise TypeError("Nodes cannot be ordered")
@@ -703,9 +706,6 @@ def __eq__(self, other):
703706
and self._value == other._value
704707
)
705708

706-
def __ne__(self, other):
707-
return not self.__eq__(other)
708-
709709
def _to_filter(self, post=False):
710710
"""Helper to convert to low-level filter.
711711

tests/unit/test_key.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,13 @@ def test___ne__():
318318
key3 = key_module.Key("X", 11, app="bar", namespace="n")
319319
key4 = key_module.Key("X", 11, app="foo", namespace="m")
320320
key5 = mock.sentinel.key
321+
key6 = key_module.Key("X", 11, app="foo", namespace="n")
321322
assert not key1 != key1
322323
assert key1 != key2
323324
assert key1 != key3
324325
assert key1 != key4
325326
assert key1 != key5
327+
assert not key1 != key6
326328

327329
@staticmethod
328330
def test___lt__():

tests/unit/test_model.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@ def test___ne__():
111111
index_prop1 = model.IndexProperty(name="d", direction="asc")
112112
index_prop2 = model.IndexProperty(name="d", direction="desc")
113113
index_prop3 = mock.sentinel.index_prop
114+
index_prop4 = model.IndexProperty(name="d", direction="asc")
114115
assert not index_prop1 != index_prop1
115116
assert index_prop1 != index_prop2
116117
assert index_prop1 != index_prop3
118+
assert not index_prop1 != index_prop4
117119

118120
@staticmethod
119121
def test___hash__():
@@ -186,11 +188,13 @@ def test___ne__():
186188
index3 = model.Index(kind="d", properties=index_props, ancestor=True)
187189
index4 = model.Index(kind="e", properties=index_props, ancestor=False)
188190
index5 = mock.sentinel.index
191+
index6 = model.Index(kind="d", properties=index_props, ancestor=False)
189192
assert not index1 != index1
190193
assert index1 != index2
191194
assert index1 != index3
192195
assert index1 != index4
193196
assert index1 != index5
197+
assert not index1 != index6
194198

195199
@staticmethod
196200
def test___hash__():
@@ -280,11 +284,15 @@ def test___ne__(self):
280284
definition=self.INDEX, state="error", id=80
281285
)
282286
index_state5 = mock.sentinel.index_state
287+
index_state6 = model.IndexState(
288+
definition=self.INDEX, state="error", id=20
289+
)
283290
assert not index_state1 != index_state1
284291
assert index_state1 != index_state2
285292
assert index_state1 != index_state3
286293
assert index_state1 != index_state4
287294
assert index_state1 != index_state5
295+
assert not index_state1 != index_state6
288296

289297
def test___hash__(self):
290298
index_state1 = model.IndexState(
@@ -354,9 +362,11 @@ def test___ne__():
354362
wrapped1 = model._BaseValue("one val")
355363
wrapped2 = model._BaseValue(25.5)
356364
wrapped3 = mock.sentinel.base_value
365+
wrapped4 = model._BaseValue("one val")
357366
assert not wrapped1 != wrapped1
358367
assert wrapped1 != wrapped2
359368
assert wrapped1 != wrapped3
369+
assert not wrapped1 != wrapped4
360370

361371
@staticmethod
362372
def test___hash__():
@@ -1621,9 +1631,11 @@ def test___ne__():
16211631
z_val2 = zlib.compress(b"12345678901234567890abcde\x00")
16221632
compressed_value2 = model._CompressedValue(z_val2)
16231633
compressed_value3 = mock.sentinel.compressed_value
1634+
compressed_value4 = model._CompressedValue(z_val1)
16241635
assert not compressed_value1 != compressed_value1
16251636
assert compressed_value1 != compressed_value2
16261637
assert compressed_value1 != compressed_value3
1638+
assert not compressed_value1 != compressed_value4
16271639

16281640
@staticmethod
16291641
def test___hash__():
@@ -4032,11 +4044,10 @@ class SomeKind(model.Model):
40324044
foo = model.StructuredProperty(OtherKind)
40334045
hi = model.StringProperty()
40344046

4035-
# entity1 = SomeKind(hi="mom", foo=OtherKind(bar=42))
4036-
# entity2 = SomeKind(hi="mom", foo=OtherKind(bar=42))
4047+
entity1 = SomeKind(hi="mom", foo=OtherKind(bar=42))
4048+
entity2 = SomeKind(hi="mom", foo=OtherKind(bar=42))
40374049

4038-
# TODO: can't figure out why this one fails
4039-
# assert entity1 == entity2
4050+
assert entity1 == entity2
40404051

40414052
@staticmethod
40424053
def test__eq__structured_property_differs():
@@ -4093,11 +4104,13 @@ class Simple(model.Model):
40934104
entity3 = ManyFields(self=-9, id="bye")
40944105
entity4 = ManyFields(self=-9, id="bye", projection=("self", "id"))
40954106
entity5 = None
4107+
entity6 = ManyFields(self=-9, id="hi")
40964108
assert not entity1 != entity1
40974109
assert entity1 != entity2
40984110
assert entity1 != entity3
40994111
assert entity1 != entity4
41004112
assert entity1 != entity5
4113+
assert not entity1 != entity6
41014114

41024115
@staticmethod
41034116
def test___lt__():

tests/unit/test_query.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,15 @@ def test___eq__():
444444
assert false_node1 == false_node2
445445
assert not false_node1 == false_node3
446446

447+
@staticmethod
448+
def test___ne__():
449+
false_node1 = query_module.FalseNode()
450+
false_node2 = query_module.FalseNode()
451+
false_node3 = mock.sentinel.false_node
452+
assert not false_node1 != false_node1
453+
assert not false_node1 != false_node2
454+
assert false_node1 != false_node3
455+
447456
@staticmethod
448457
def test__to_filter():
449458
false_node = query_module.FalseNode()
@@ -522,6 +531,24 @@ def test___eq__():
522531
assert not parameter_node1 == parameter_node4
523532
assert not parameter_node1 == parameter_node5
524533

534+
@staticmethod
535+
def test___ne__():
536+
prop1 = model.Property(name="val")
537+
param1 = query_module.Parameter("abc")
538+
parameter_node1 = query_module.ParameterNode(prop1, "=", param1)
539+
prop2 = model.Property(name="ue")
540+
parameter_node2 = query_module.ParameterNode(prop2, "=", param1)
541+
parameter_node3 = query_module.ParameterNode(prop1, "<", param1)
542+
param2 = query_module.Parameter(900)
543+
parameter_node4 = query_module.ParameterNode(prop1, "=", param2)
544+
parameter_node5 = mock.sentinel.parameter_node
545+
546+
assert not parameter_node1 != parameter_node1
547+
assert parameter_node1 != parameter_node2
548+
assert parameter_node1 != parameter_node3
549+
assert parameter_node1 != parameter_node4
550+
assert parameter_node1 != parameter_node5
551+
525552
@staticmethod
526553
def test__to_filter():
527554
prop = model.Property(name="val")
@@ -712,6 +739,17 @@ def test___eq__():
712739
assert not post_filter_node1 == post_filter_node2
713740
assert not post_filter_node1 == post_filter_node3
714741

742+
@staticmethod
743+
def test___ne__():
744+
predicate1 = mock.sentinel.predicate1
745+
post_filter_node1 = query_module.PostFilterNode(predicate1)
746+
predicate2 = mock.sentinel.predicate2
747+
post_filter_node2 = query_module.PostFilterNode(predicate2)
748+
post_filter_node3 = mock.sentinel.post_filter_node
749+
assert not post_filter_node1 != post_filter_node1
750+
assert post_filter_node1 != post_filter_node2
751+
assert post_filter_node1 != post_filter_node3
752+
715753
@staticmethod
716754
def test__to_filter_post():
717755
predicate = mock.sentinel.predicate
@@ -911,6 +949,22 @@ def test___eq__():
911949
assert not and_node1 == and_node3
912950
assert not and_node1 == and_node4
913951

952+
@staticmethod
953+
def test___ne__():
954+
filter_node1 = query_module.FilterNode("a", "=", 7)
955+
filter_node2 = query_module.FilterNode("b", ">", 7.5)
956+
filter_node3 = query_module.FilterNode("c", "<", "now")
957+
958+
and_node1 = query_module.ConjunctionNode(filter_node1, filter_node2)
959+
and_node2 = query_module.ConjunctionNode(filter_node2, filter_node1)
960+
and_node3 = query_module.ConjunctionNode(filter_node1, filter_node3)
961+
and_node4 = mock.sentinel.and_node
962+
963+
assert not and_node1 != and_node1
964+
assert and_node1 != and_node2
965+
assert and_node1 != and_node3
966+
assert and_node1 != and_node4
967+
914968
@staticmethod
915969
def test__to_filter_empty():
916970
node1 = query_module.FilterNode("a", "=", 7)
@@ -1100,6 +1154,22 @@ def test___eq__():
11001154
assert not or_node1 == or_node3
11011155
assert not or_node1 == or_node4
11021156

1157+
@staticmethod
1158+
def test___ne__():
1159+
filter_node1 = query_module.FilterNode("a", "=", 7)
1160+
filter_node2 = query_module.FilterNode("b", ">", 7.5)
1161+
filter_node3 = query_module.FilterNode("c", "<", "now")
1162+
1163+
or_node1 = query_module.DisjunctionNode(filter_node1, filter_node2)
1164+
or_node2 = query_module.DisjunctionNode(filter_node2, filter_node1)
1165+
or_node3 = query_module.DisjunctionNode(filter_node1, filter_node3)
1166+
or_node4 = mock.sentinel.or_node
1167+
1168+
assert not or_node1 != or_node1
1169+
assert or_node1 != or_node2
1170+
assert or_node1 != or_node3
1171+
assert or_node1 != or_node4
1172+
11031173
@staticmethod
11041174
def test_resolve():
11051175
node1 = query_module.FilterNode("a", "=", 7)

0 commit comments

Comments
 (0)