Skip to content

Commit d4070ca

Browse files
hrkhLinchin
andauthored
feat: support resource_tags for table (#2093)
* feat: support resource_tags for table * fix: system test for resource tags * fix: typo * fix: unit test * Update tests/unit/test_client.py * Update google/cloud/bigquery/table.py * Update google/cloud/bigquery/table.py * Update google/cloud/bigquery/table.py * fix: append random string suffix to resource tags to prevent test conflicts * Update google/cloud/bigquery/table.py --------- Co-authored-by: Lingqing Gan <[email protected]>
1 parent b44fda0 commit d4070ca

File tree

4 files changed

+91
-3
lines changed

4 files changed

+91
-3
lines changed

google/cloud/bigquery/table.py

+17
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ class Table(_TableBase):
409409
"require_partition_filter": "requirePartitionFilter",
410410
"table_constraints": "tableConstraints",
411411
"max_staleness": "maxStaleness",
412+
"resource_tags": "resourceTags",
412413
"external_catalog_table_options": "externalCatalogTableOptions",
413414
}
414415

@@ -1025,6 +1026,22 @@ def table_constraints(self) -> Optional["TableConstraints"]:
10251026
table_constraints = TableConstraints.from_api_repr(table_constraints)
10261027
return table_constraints
10271028

1029+
@property
1030+
def resource_tags(self):
1031+
"""Dict[str, str]: Resource tags for the table.
1032+
1033+
See: https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#Table.FIELDS.resource_tags
1034+
"""
1035+
return self._properties.setdefault(
1036+
self._PROPERTY_TO_API_FIELD["resource_tags"], {}
1037+
)
1038+
1039+
@resource_tags.setter
1040+
def resource_tags(self, value):
1041+
if not isinstance(value, dict) and value is not None:
1042+
raise ValueError("resource_tags must be a dict or None")
1043+
self._properties[self._PROPERTY_TO_API_FIELD["resource_tags"]] = value
1044+
10281045
@property
10291046
def external_catalog_table_options(
10301047
self,

tests/system/test_client.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,16 @@ def test_list_tables(self):
732732
def test_update_table(self):
733733
dataset = self.temp_dataset(_make_dataset_id("update_table"))
734734

735+
# This creates unique tag keys for each of test runnings for different Python versions
736+
tag_postfix = "".join(random.choices(string.ascii_letters + string.digits, k=4))
737+
tag_1 = f"owner_{tag_postfix}"
738+
tag_2 = f"classification_{tag_postfix}"
739+
tag_3 = f"env_{tag_postfix}"
740+
741+
self._create_resource_tag_key_and_values(tag_1, ["Alice", "Bob"])
742+
self._create_resource_tag_key_and_values(tag_2, ["public"])
743+
self._create_resource_tag_key_and_values(tag_3, ["dev"])
744+
735745
TABLE_NAME = "test_table"
736746
table_arg = Table(dataset.table(TABLE_NAME), schema=SCHEMA)
737747
self.assertFalse(_table_exists(table_arg))
@@ -744,24 +754,54 @@ def test_update_table(self):
744754
table.friendly_name = "Friendly"
745755
table.description = "Description"
746756
table.labels = {"priority": "high", "color": "blue"}
757+
table.resource_tags = {
758+
f"{Config.CLIENT.project}/{tag_1}": "Alice",
759+
f"{Config.CLIENT.project}/{tag_3}": "dev",
760+
}
747761

748762
table2 = Config.CLIENT.update_table(
749-
table, ["friendly_name", "description", "labels"]
763+
table, ["friendly_name", "description", "labels", "resource_tags"]
750764
)
751765

752766
self.assertEqual(table2.friendly_name, "Friendly")
753767
self.assertEqual(table2.description, "Description")
754768
self.assertEqual(table2.labels, {"priority": "high", "color": "blue"})
769+
self.assertEqual(
770+
table2.resource_tags,
771+
{
772+
f"{Config.CLIENT.project}/{tag_1}": "Alice",
773+
f"{Config.CLIENT.project}/{tag_3}": "dev",
774+
},
775+
)
755776

756777
table2.description = None
757778
table2.labels = {
758779
"color": "green", # change
759780
"shape": "circle", # add
760781
"priority": None, # delete
761782
}
762-
table3 = Config.CLIENT.update_table(table2, ["description", "labels"])
783+
table2.resource_tags = {
784+
f"{Config.CLIENT.project}/{tag_1}": "Bob", # change
785+
f"{Config.CLIENT.project}/{tag_2}": "public", # add
786+
f"{Config.CLIENT.project}/{tag_3}": None, # delete
787+
}
788+
table3 = Config.CLIENT.update_table(
789+
table2, ["description", "labels", "resource_tags"]
790+
)
763791
self.assertIsNone(table3.description)
764792
self.assertEqual(table3.labels, {"color": "green", "shape": "circle"})
793+
self.assertEqual(
794+
table3.resource_tags,
795+
{
796+
f"{Config.CLIENT.project}/{tag_1}": "Bob",
797+
f"{Config.CLIENT.project}/{tag_2}": "public",
798+
},
799+
)
800+
801+
# Delete resource tag bindings.
802+
table3.resource_tags = None
803+
table4 = Config.CLIENT.update_table(table3, ["resource_tags"])
804+
self.assertEqual(table4.resource_tags, {})
765805

766806
# If we try to update using table2 again, it will fail because the
767807
# previous update changed the ETag.

tests/unit/test_client.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -2320,6 +2320,7 @@ def test_update_table(self):
23202320
"description": description,
23212321
"friendlyName": title,
23222322
"labels": {"x": "y"},
2323+
"resourceTags": {"123456789012/key": "value"},
23232324
}
23242325
)
23252326
schema = [
@@ -2343,7 +2344,8 @@ def test_update_table(self):
23432344
table.description = description
23442345
table.friendly_name = title
23452346
table.labels = {"x": "y"}
2346-
fields = ["schema", "description", "friendly_name", "labels"]
2347+
table.resource_tags = {"123456789012/key": "value"}
2348+
fields = ["schema", "description", "friendly_name", "labels", "resource_tags"]
23472349
with mock.patch(
23482350
"google.cloud.bigquery.opentelemetry_tracing._get_final_span_attributes"
23492351
) as final_attributes:
@@ -2375,6 +2377,7 @@ def test_update_table(self):
23752377
"description": description,
23762378
"friendlyName": title,
23772379
"labels": {"x": "y"},
2380+
"resourceTags": {"123456789012/key": "value"},
23782381
}
23792382
conn.api_request.assert_called_once_with(
23802383
method="PATCH", data=sent, path="/" + path, timeout=7.5
@@ -2383,6 +2386,7 @@ def test_update_table(self):
23832386
self.assertEqual(updated_table.friendly_name, table.friendly_name)
23842387
self.assertEqual(updated_table.schema, table.schema)
23852388
self.assertEqual(updated_table.labels, table.labels)
2389+
self.assertEqual(updated_table.resource_tags, table.resource_tags)
23862390

23872391
# ETag becomes If-Match header.
23882392
table._properties["etag"] = "etag"

tests/unit/test_table.py

+27
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,33 @@ def test_encryption_configuration_setter(self):
14811481
table.encryption_configuration = None
14821482
self.assertIsNone(table.encryption_configuration)
14831483

1484+
def test_resource_tags_getter_empty(self):
1485+
dataset = DatasetReference(self.PROJECT, self.DS_ID)
1486+
table_ref = dataset.table(self.TABLE_NAME)
1487+
table = self._make_one(table_ref)
1488+
self.assertEqual(table.resource_tags, {})
1489+
1490+
def test_resource_tags_update_in_place(self):
1491+
dataset = DatasetReference(self.PROJECT, self.DS_ID)
1492+
table_ref = dataset.table(self.TABLE_NAME)
1493+
table = self._make_one(table_ref)
1494+
table.resource_tags["123456789012/key"] = "value"
1495+
self.assertEqual(table.resource_tags, {"123456789012/key": "value"})
1496+
1497+
def test_resource_tags_setter(self):
1498+
dataset = DatasetReference(self.PROJECT, self.DS_ID)
1499+
table_ref = dataset.table(self.TABLE_NAME)
1500+
table = self._make_one(table_ref)
1501+
table.resource_tags = {"123456789012/key": "value"}
1502+
self.assertEqual(table.resource_tags, {"123456789012/key": "value"})
1503+
1504+
def test_resource_tags_setter_bad_value(self):
1505+
dataset = DatasetReference(self.PROJECT, self.DS_ID)
1506+
table_ref = dataset.table(self.TABLE_NAME)
1507+
table = self._make_one(table_ref)
1508+
with self.assertRaises(ValueError):
1509+
table.resource_tags = 12345
1510+
14841511
def test___repr__(self):
14851512
from google.cloud.bigquery.table import TableReference
14861513

0 commit comments

Comments
 (0)