Skip to content

Commit c1a3d44

Browse files
authored
feat: make the same Table* instances equal to each other (#867)
* feat: make the same Table instances equal to each other * Table equality should ignore metadata differences * Compare instances through tableReference property * Make Table instances hashable * Make Table* classes interchangeable If these classes reference the same table, they are now considered equal.
1 parent aee814c commit c1a3d44

File tree

2 files changed

+244
-23
lines changed

2 files changed

+244
-23
lines changed

google/cloud/bigquery/table.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,16 @@ def _key(self):
255255
return (self._project, self._dataset_id, self._table_id)
256256

257257
def __eq__(self, other):
258-
if not isinstance(other, TableReference):
258+
if isinstance(other, (Table, TableListItem)):
259+
return (
260+
self.project == other.project
261+
and self.dataset_id == other.dataset_id
262+
and self.table_id == other.table_id
263+
)
264+
elif isinstance(other, TableReference):
265+
return self._key() == other._key()
266+
else:
259267
return NotImplemented
260-
return self._key() == other._key()
261268

262269
def __ne__(self, other):
263270
return not self == other
@@ -1011,6 +1018,24 @@ def _build_resource(self, filter_fields):
10111018
"""Generate a resource for ``update``."""
10121019
return _helpers._build_resource_from_properties(self, filter_fields)
10131020

1021+
def __eq__(self, other):
1022+
if isinstance(other, Table):
1023+
return (
1024+
self._properties["tableReference"]
1025+
== other._properties["tableReference"]
1026+
)
1027+
elif isinstance(other, (TableReference, TableListItem)):
1028+
return (
1029+
self.project == other.project
1030+
and self.dataset_id == other.dataset_id
1031+
and self.table_id == other.table_id
1032+
)
1033+
else:
1034+
return NotImplemented
1035+
1036+
def __hash__(self):
1037+
return hash((self.project, self.dataset_id, self.table_id))
1038+
10141039
def __repr__(self):
10151040
return "Table({})".format(repr(self.reference))
10161041

@@ -1229,6 +1254,19 @@ def to_api_repr(self) -> dict:
12291254
"""
12301255
return copy.deepcopy(self._properties)
12311256

1257+
def __eq__(self, other):
1258+
if isinstance(other, (Table, TableReference, TableListItem)):
1259+
return (
1260+
self.project == other.project
1261+
and self.dataset_id == other.dataset_id
1262+
and self.table_id == other.table_id
1263+
)
1264+
else:
1265+
return NotImplemented
1266+
1267+
def __hash__(self):
1268+
return hash((self.project, self.dataset_id, self.table_id))
1269+
12321270

12331271
def _row_from_mapping(mapping, schema):
12341272
"""Convert a mapping to a row tuple using the schema.

tests/unit/test_table.py

Lines changed: 204 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,13 @@ def _make_one(self, *args, **kw):
115115
return self._get_target_class()(*args, **kw)
116116

117117
def test_ctor_defaults(self):
118-
from google.cloud.bigquery.dataset import DatasetReference
119-
120118
dataset_ref = DatasetReference("project_1", "dataset_1")
121119

122120
table_ref = self._make_one(dataset_ref, "table_1")
123121
self.assertEqual(table_ref.dataset_id, dataset_ref.dataset_id)
124122
self.assertEqual(table_ref.table_id, "table_1")
125123

126124
def test_to_api_repr(self):
127-
from google.cloud.bigquery.dataset import DatasetReference
128-
129125
dataset_ref = DatasetReference("project_1", "dataset_1")
130126
table_ref = self._make_one(dataset_ref, "table_1")
131127

@@ -137,7 +133,6 @@ def test_to_api_repr(self):
137133
)
138134

139135
def test_from_api_repr(self):
140-
from google.cloud.bigquery.dataset import DatasetReference
141136
from google.cloud.bigquery.table import TableReference
142137

143138
dataset_ref = DatasetReference("project_1", "dataset_1")
@@ -204,51 +199,39 @@ def test_from_string_ignores_default_project(self):
204199
self.assertEqual(got.table_id, "string_table")
205200

206201
def test___eq___wrong_type(self):
207-
from google.cloud.bigquery.dataset import DatasetReference
208-
209202
dataset_ref = DatasetReference("project_1", "dataset_1")
210203
table = self._make_one(dataset_ref, "table_1")
211204
other = object()
212205
self.assertNotEqual(table, other)
213206
self.assertEqual(table, mock.ANY)
214207

215208
def test___eq___project_mismatch(self):
216-
from google.cloud.bigquery.dataset import DatasetReference
217-
218209
dataset = DatasetReference("project_1", "dataset_1")
219210
other_dataset = DatasetReference("project_2", "dataset_1")
220211
table = self._make_one(dataset, "table_1")
221212
other = self._make_one(other_dataset, "table_1")
222213
self.assertNotEqual(table, other)
223214

224215
def test___eq___dataset_mismatch(self):
225-
from google.cloud.bigquery.dataset import DatasetReference
226-
227216
dataset = DatasetReference("project_1", "dataset_1")
228217
other_dataset = DatasetReference("project_1", "dataset_2")
229218
table = self._make_one(dataset, "table_1")
230219
other = self._make_one(other_dataset, "table_1")
231220
self.assertNotEqual(table, other)
232221

233222
def test___eq___table_mismatch(self):
234-
from google.cloud.bigquery.dataset import DatasetReference
235-
236223
dataset = DatasetReference("project_1", "dataset_1")
237224
table = self._make_one(dataset, "table_1")
238225
other = self._make_one(dataset, "table_2")
239226
self.assertNotEqual(table, other)
240227

241228
def test___eq___equality(self):
242-
from google.cloud.bigquery.dataset import DatasetReference
243-
244229
dataset = DatasetReference("project_1", "dataset_1")
245230
table = self._make_one(dataset, "table_1")
246231
other = self._make_one(dataset, "table_1")
247232
self.assertEqual(table, other)
248233

249234
def test___hash__set_equality(self):
250-
from google.cloud.bigquery.dataset import DatasetReference
251-
252235
dataset = DatasetReference("project_1", "dataset_1")
253236
table1 = self._make_one(dataset, "table1")
254237
table2 = self._make_one(dataset, "table2")
@@ -257,8 +240,6 @@ def test___hash__set_equality(self):
257240
self.assertEqual(set_one, set_two)
258241

259242
def test___hash__not_equals(self):
260-
from google.cloud.bigquery.dataset import DatasetReference
261-
262243
dataset = DatasetReference("project_1", "dataset_1")
263244
table1 = self._make_one(dataset, "table1")
264245
table2 = self._make_one(dataset, "table2")
@@ -294,8 +275,6 @@ def _get_target_class():
294275
return Table
295276

296277
def _make_one(self, *args, **kw):
297-
from google.cloud.bigquery.dataset import DatasetReference
298-
299278
if len(args) == 0:
300279
dataset = DatasetReference(self.PROJECT, self.DS_ID)
301280
table_ref = dataset.table(self.TABLE_NAME)
@@ -581,6 +560,68 @@ def test_num_rows_getter(self):
581560
with self.assertRaises(ValueError):
582561
getattr(table, "num_rows")
583562

563+
def test__eq__wrong_type(self):
564+
table = self._make_one("project_foo.dataset_bar.table_baz")
565+
566+
class TableWannabe:
567+
pass
568+
569+
not_a_table = TableWannabe()
570+
not_a_table._properties = table._properties
571+
572+
assert table != not_a_table # Can't fake it.
573+
574+
def test__eq__same_table_basic(self):
575+
table_1 = self._make_one("project_foo.dataset_bar.table_baz")
576+
table_2 = self._make_one("project_foo.dataset_bar.table_baz")
577+
assert table_1 == table_2
578+
579+
def test__eq__same_table_multiple_properties(self):
580+
from google.cloud.bigquery import SchemaField
581+
582+
table_1 = self._make_one("project_foo.dataset_bar.table_baz")
583+
table_1.require_partition_filter = True
584+
table_1.labels = {"first": "one", "second": "two"}
585+
586+
table_1.schema = [
587+
SchemaField("name", "STRING", "REQUIRED"),
588+
SchemaField("age", "INTEGER", "NULLABLE"),
589+
]
590+
591+
table_2 = self._make_one("project_foo.dataset_bar.table_baz")
592+
table_2.require_partition_filter = True
593+
table_2.labels = {"first": "one", "second": "two"}
594+
table_2.schema = [
595+
SchemaField("name", "STRING", "REQUIRED"),
596+
SchemaField("age", "INTEGER", "NULLABLE"),
597+
]
598+
599+
assert table_1 == table_2
600+
601+
def test__eq__same_table_property_different(self):
602+
table_1 = self._make_one("project_foo.dataset_bar.table_baz")
603+
table_1.description = "This is table baz"
604+
605+
table_2 = self._make_one("project_foo.dataset_bar.table_baz")
606+
table_2.description = "This is also table baz"
607+
608+
assert table_1 == table_2 # Still equal, only table reference is important.
609+
610+
def test__eq__different_table(self):
611+
table_1 = self._make_one("project_foo.dataset_bar.table_baz")
612+
table_2 = self._make_one("project_foo.dataset_bar.table_baz_2")
613+
614+
assert table_1 != table_2
615+
616+
def test_hashable(self):
617+
table_1 = self._make_one("project_foo.dataset_bar.table_baz")
618+
table_1.description = "This is a table"
619+
620+
table_1b = self._make_one("project_foo.dataset_bar.table_baz")
621+
table_1b.description = "Metadata is irrelevant for hashes"
622+
623+
assert hash(table_1) == hash(table_1b)
624+
584625
def test_schema_setter_non_sequence(self):
585626
dataset = DatasetReference(self.PROJECT, self.DS_ID)
586627
table_ref = dataset.table(self.TABLE_NAME)
@@ -1543,6 +1584,148 @@ def test_to_api_repr(self):
15431584
table = self._make_one(resource)
15441585
self.assertEqual(table.to_api_repr(), resource)
15451586

1587+
def test__eq__wrong_type(self):
1588+
resource = {
1589+
"tableReference": {
1590+
"projectId": "project_foo",
1591+
"datasetId": "dataset_bar",
1592+
"tableId": "table_baz",
1593+
}
1594+
}
1595+
table = self._make_one(resource)
1596+
1597+
class FakeTableListItem:
1598+
project = "project_foo"
1599+
dataset_id = "dataset_bar"
1600+
table_id = "table_baz"
1601+
1602+
not_a_table = FakeTableListItem()
1603+
1604+
assert table != not_a_table # Can't fake it.
1605+
1606+
def test__eq__same_table(self):
1607+
resource = {
1608+
"tableReference": {
1609+
"projectId": "project_foo",
1610+
"datasetId": "dataset_bar",
1611+
"tableId": "table_baz",
1612+
}
1613+
}
1614+
table_1 = self._make_one(resource)
1615+
table_2 = self._make_one(resource)
1616+
1617+
assert table_1 == table_2
1618+
1619+
def test__eq__same_table_property_different(self):
1620+
table_ref_resource = {
1621+
"projectId": "project_foo",
1622+
"datasetId": "dataset_bar",
1623+
"tableId": "table_baz",
1624+
}
1625+
1626+
resource_1 = {"tableReference": table_ref_resource, "friendlyName": "Table One"}
1627+
table_1 = self._make_one(resource_1)
1628+
1629+
resource_2 = {"tableReference": table_ref_resource, "friendlyName": "Table Two"}
1630+
table_2 = self._make_one(resource_2)
1631+
1632+
assert table_1 == table_2 # Still equal, only table reference is important.
1633+
1634+
def test__eq__different_table(self):
1635+
resource_1 = {
1636+
"tableReference": {
1637+
"projectId": "project_foo",
1638+
"datasetId": "dataset_bar",
1639+
"tableId": "table_baz",
1640+
}
1641+
}
1642+
table_1 = self._make_one(resource_1)
1643+
1644+
resource_2 = {
1645+
"tableReference": {
1646+
"projectId": "project_foo",
1647+
"datasetId": "dataset_bar",
1648+
"tableId": "table_quux",
1649+
}
1650+
}
1651+
table_2 = self._make_one(resource_2)
1652+
1653+
assert table_1 != table_2
1654+
1655+
def test_hashable(self):
1656+
resource = {
1657+
"tableReference": {
1658+
"projectId": "project_foo",
1659+
"datasetId": "dataset_bar",
1660+
"tableId": "table_baz",
1661+
}
1662+
}
1663+
table_item = self._make_one(resource)
1664+
table_item_2 = self._make_one(resource)
1665+
1666+
assert hash(table_item) == hash(table_item_2)
1667+
1668+
1669+
class TestTableClassesInterchangeability:
1670+
@staticmethod
1671+
def _make_table(*args, **kwargs):
1672+
from google.cloud.bigquery.table import Table
1673+
1674+
return Table(*args, **kwargs)
1675+
1676+
@staticmethod
1677+
def _make_table_ref(*args, **kwargs):
1678+
from google.cloud.bigquery.table import TableReference
1679+
1680+
return TableReference(*args, **kwargs)
1681+
1682+
@staticmethod
1683+
def _make_table_list_item(*args, **kwargs):
1684+
from google.cloud.bigquery.table import TableListItem
1685+
1686+
return TableListItem(*args, **kwargs)
1687+
1688+
def test_table_eq_table_ref(self):
1689+
1690+
table = self._make_table("project_foo.dataset_bar.table_baz")
1691+
dataset_ref = DatasetReference("project_foo", "dataset_bar")
1692+
table_ref = self._make_table_ref(dataset_ref, "table_baz")
1693+
1694+
assert table == table_ref
1695+
assert table_ref == table
1696+
1697+
def test_table_eq_table_list_item(self):
1698+
table = self._make_table("project_foo.dataset_bar.table_baz")
1699+
table_list_item = self._make_table_list_item(
1700+
{
1701+
"tableReference": {
1702+
"projectId": "project_foo",
1703+
"datasetId": "dataset_bar",
1704+
"tableId": "table_baz",
1705+
}
1706+
}
1707+
)
1708+
1709+
assert table == table_list_item
1710+
assert table_list_item == table
1711+
1712+
def test_table_ref_eq_table_list_item(self):
1713+
1714+
dataset_ref = DatasetReference("project_foo", "dataset_bar")
1715+
table_ref = self._make_table_ref(dataset_ref, "table_baz")
1716+
table_list_item = self._make_table_list_item(
1717+
{
1718+
"tableReference": {
1719+
"projectId": "project_foo",
1720+
"datasetId": "dataset_bar",
1721+
"tableId": "table_baz",
1722+
}
1723+
}
1724+
)
1725+
1726+
assert table_ref == table_list_item
1727+
assert table_list_item == table_ref
1728+
15461729

15471730
class TestSnapshotDefinition:
15481731
@staticmethod

0 commit comments

Comments
 (0)