Repository: googleapis/python-bigquery

Issue:
Comparaison between RangePartitioning objects seems to be incorrect.
Comparaison between RangePartitioning objects seems to be incorrect.

#### Environment details

  - `google-cloud-bigquery` version: `1.25.0`

#### Code example

```python
from google.cloud.bigquery import RangePartitioning

print(RangePartitioning(field='something') ==
      RangePartitioning(field='something'))
```
Code above returns `False`. I was expecting `True`.


Discussion:
@francois-baptiste  The magic of the equality operator == happens in the __eq__() class method of the object. This is a magic class method that’s called whenever an instance of this class is compared against another object. If this method is not implemented in the class , then == compares the memory addresses of the two objects by default.

You can compare like: 

    print (repr(RangePartitioning(field='something')) ==  repr(RangePartitioning(field='something')))
Thank you @HemangChothani for the workaround. I think however that the eq() method should be implemented as the other class of the library.

Resolving patch:
diff --git a/google/cloud/bigquery/table.py b/google/cloud/bigquery/table.py
--- a/google/cloud/bigquery/table.py
+++ b/google/cloud/bigquery/table.py
@@ -1891,10 +1891,20 @@ def interval(self, value):
     def _key(self):
         return tuple(sorted(self._properties.items()))
 
+    def __eq__(self, other):
+        if not isinstance(other, PartitionRange):
+            return NotImplemented
+        return self._key() == other._key()
+
+    def __ne__(self, other):
+        return not self == other
+
     def __repr__(self):
         key_vals = ["{}={}".format(key, val) for key, val in self._key()]
         return "PartitionRange({})".format(", ".join(key_vals))
 
+    __hash__ = None
+
 
 class RangePartitioning(object):
     """Range-based partitioning configuration for a table.
@@ -1961,10 +1971,20 @@ def field(self, value):
     def _key(self):
         return (("field", self.field), ("range_", self.range_))
 
+    def __eq__(self, other):
+        if not isinstance(other, RangePartitioning):
+            return NotImplemented
+        return self._key() == other._key()
+
+    def __ne__(self, other):
+        return not self == other
+
     def __repr__(self):
         key_vals = ["{}={}".format(key, repr(val)) for key, val in self._key()]
         return "RangePartitioning({})".format(", ".join(key_vals))
 
+    __hash__ = None
+
 
 class TimePartitioningType(object):
     """Specifies the type of time partitioning to perform."""


Test patch:
diff --git a/tests/unit/test_table.py b/tests/unit/test_table.py
--- a/tests/unit/test_table.py
+++ b/tests/unit/test_table.py
@@ -3525,6 +3525,37 @@ def test_constructor_w_resource(self):
         assert object_under_test.end == 1234567890
         assert object_under_test.interval == 1000000
 
+    def test___eq___start_mismatch(self):
+        object_under_test = self._make_one(start=1, end=10, interval=2)
+        other = self._make_one(start=2, end=10, interval=2)
+        self.assertNotEqual(object_under_test, other)
+
+    def test___eq___end__mismatch(self):
+        object_under_test = self._make_one(start=1, end=10, interval=2)
+        other = self._make_one(start=1, end=11, interval=2)
+        self.assertNotEqual(object_under_test, other)
+
+    def test___eq___interval__mismatch(self):
+        object_under_test = self._make_one(start=1, end=10, interval=2)
+        other = self._make_one(start=1, end=11, interval=3)
+        self.assertNotEqual(object_under_test, other)
+
+    def test___eq___hit(self):
+        object_under_test = self._make_one(start=1, end=10, interval=2)
+        other = self._make_one(start=1, end=10, interval=2)
+        self.assertEqual(object_under_test, other)
+
+    def test__eq___type_mismatch(self):
+        object_under_test = self._make_one(start=1, end=10, interval=2)
+        self.assertNotEqual(object_under_test, object())
+        self.assertEqual(object_under_test, mock.ANY)
+
+    def test_unhashable_object(self):
+        object_under_test1 = self._make_one(start=1, end=10, interval=2)
+
+        with six.assertRaisesRegex(self, TypeError, r".*unhashable type.*"):
+            hash(object_under_test1)
+
     def test_repr(self):
         object_under_test = self._make_one(start=1, end=10, interval=2)
         assert repr(object_under_test) == "PartitionRange(end=10, interval=2, start=1)"
@@ -3574,6 +3605,57 @@ def test_range_w_wrong_type(self):
         with pytest.raises(ValueError, match="PartitionRange"):
             object_under_test.range_ = object()
 
+    def test___eq___field_mismatch(self):
+        from google.cloud.bigquery.table import PartitionRange
+
+        object_under_test = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="integer_col"
+        )
+        other = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="float_col"
+        )
+        self.assertNotEqual(object_under_test, other)
+
+    def test___eq___range__mismatch(self):
+        from google.cloud.bigquery.table import PartitionRange
+
+        object_under_test = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="integer_col"
+        )
+        other = self._make_one(
+            range_=PartitionRange(start=2, end=20, interval=2), field="float_col"
+        )
+        self.assertNotEqual(object_under_test, other)
+
+    def test___eq___hit(self):
+        from google.cloud.bigquery.table import PartitionRange
+
+        object_under_test = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="integer_col"
+        )
+        other = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="integer_col"
+        )
+        self.assertEqual(object_under_test, other)
+
+    def test__eq___type_mismatch(self):
+        from google.cloud.bigquery.table import PartitionRange
+
+        object_under_test = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="integer_col"
+        )
+        self.assertNotEqual(object_under_test, object())
+        self.assertEqual(object_under_test, mock.ANY)
+
+    def test_unhashable_object(self):
+        from google.cloud.bigquery.table import PartitionRange
+
+        object_under_test1 = self._make_one(
+            range_=PartitionRange(start=1, end=10, interval=2), field="integer_col"
+        )
+        with six.assertRaisesRegex(self, TypeError, r".*unhashable type.*"):
+            hash(object_under_test1)
+
     def test_repr(self):
         from google.cloud.bigquery.table import PartitionRange