Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable unsetting policy tags on schema fields #703

Merged
merged 3 commits into from Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 9 additions & 6 deletions google/cloud/bigquery/schema.py
Expand Up @@ -105,7 +105,11 @@ def __init__(
if max_length is not _DEFAULT_VALUE:
self._properties["maxLength"] = max_length
self._fields = tuple(fields)
self._policy_tags = policy_tags

if policy_tags is None:
self._policy_tags = PolicyTagList()
else:
self._policy_tags = policy_tags
plamut marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def __get_int(api_repr, name):
Expand Down Expand Up @@ -201,7 +205,7 @@ def fields(self):

@property
def policy_tags(self):
"""Optional[google.cloud.bigquery.schema.PolicyTagList]: Policy tag list
"""google.cloud.bigquery.schema.PolicyTagList: Policy tag list
definition for this field.
"""
return self._policy_tags
Expand All @@ -219,9 +223,8 @@ def to_api_repr(self) -> dict:
if self.field_type.upper() in _STRUCT_TYPES:
answer["fields"] = [f.to_api_repr() for f in self.fields]

# If this contains a policy tag definition, include that as well:
if self.policy_tags is not None:
answer["policyTags"] = self.policy_tags.to_api_repr()
# Also include policy tag definition, even if empty.
answer["policyTags"] = self.policy_tags.to_api_repr()

# Done; return the serialized dictionary.
return answer
Expand Down Expand Up @@ -251,7 +254,7 @@ def _key(self):
self.mode.upper(), # pytype: disable=attribute-error
self.description,
self._fields,
self._policy_tags,
tuple(sorted(self._policy_tags.names)),
)

def to_standard_sql(self) -> types.StandardSqlField:
Expand Down
50 changes: 50 additions & 0 deletions tests/system/test_client.py
Expand Up @@ -653,6 +653,56 @@ def test_update_table_schema(self):
self.assertEqual(found.field_type, expected.field_type)
self.assertEqual(found.mode, expected.mode)

def test_unset_table_schema_attributes(self):
from google.cloud.bigquery.schema import PolicyTagList

dataset = self.temp_dataset(_make_dataset_id("unset_policy_tags"))
table_id = "test_table"
policy_tags = PolicyTagList(
names=[
"projects/{}/locations/us/taxonomies/1/policyTags/2".format(
Config.CLIENT.project
),
]
)

schema = [
bigquery.SchemaField("full_name", "STRING", mode="REQUIRED"),
bigquery.SchemaField(
"secret_int",
"INTEGER",
mode="REQUIRED",
description="This field is numeric",
policy_tags=policy_tags,
),
]
table_arg = Table(dataset.table(table_id), schema=schema)
self.assertFalse(_table_exists(table_arg))

table = helpers.retry_403(Config.CLIENT.create_table)(table_arg)
self.to_delete.insert(0, table)

self.assertTrue(_table_exists(table))
self.assertEqual(policy_tags, table.schema[1].policy_tags)

# Amend the schema to replace the policy tags
new_schema = table.schema[:]
old_field = table.schema[1]
new_schema[1] = bigquery.SchemaField(
name=old_field.name,
field_type=old_field.field_type,
mode=old_field.mode,
description=None,
fields=old_field.fields,
policy_tags=None,
)

table.schema = new_schema
updated_table = Config.CLIENT.update_table(table, ["schema"])

self.assertFalse(updated_table.schema[1].description) # Empty string or None.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed there's a difference between when if description is not set (or set to None), or when set to an empty string. Does this matter?

In any case, it is possible to override the existing description, thus no extra code changes necessary for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this matters. In fact, it's what requested in #558 (comment)

Similar to what I did for description in #557, we should disambiguate between unset policy tags and empty / none for policy tags.

self.assertEqual(updated_table.schema[1].policy_tags.names, ())

def test_update_table_clustering_configuration(self):
dataset = self.temp_dataset(_make_dataset_id("update_table"))

Expand Down
4 changes: 4 additions & 0 deletions tests/unit/job/test_load_config.py
Expand Up @@ -434,11 +434,13 @@ def test_schema_setter_fields(self):
"name": "full_name",
"type": "STRING",
"mode": "REQUIRED",
"policyTags": {"names": []},
}
age_repr = {
"name": "age",
"type": "INTEGER",
"mode": "REQUIRED",
"policyTags": {"names": []},
}
self.assertEqual(
config._properties["load"]["schema"], {"fields": [full_name_repr, age_repr]}
Expand All @@ -451,11 +453,13 @@ def test_schema_setter_valid_mappings_list(self):
"name": "full_name",
"type": "STRING",
"mode": "REQUIRED",
"policyTags": {"names": []},
}
age_repr = {
"name": "age",
"type": "INTEGER",
"mode": "REQUIRED",
"policyTags": {"names": []},
}
schema = [full_name_repr, age_repr]
config.schema = schema
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/test_client.py
Expand Up @@ -7718,18 +7718,21 @@ def test_schema_to_json_with_file_path(self):
"description": "quarter",
"mode": "REQUIRED",
"name": "qtr",
"policyTags": {"names": []},
"type": "STRING",
},
{
"description": "sales representative",
"mode": "NULLABLE",
"name": "rep",
"policyTags": {"names": []},
"type": "STRING",
},
{
"description": "total sales",
"mode": "NULLABLE",
"name": "sales",
"policyTags": {"names": []},
"type": "FLOAT",
},
]
Expand Down Expand Up @@ -7762,18 +7765,21 @@ def test_schema_to_json_with_file_object(self):
"description": "quarter",
"mode": "REQUIRED",
"name": "qtr",
"policyTags": {"names": []},
"type": "STRING",
},
{
"description": "sales representative",
"mode": "NULLABLE",
"name": "rep",
"policyTags": {"names": []},
"type": "STRING",
},
{
"description": "total sales",
"mode": "NULLABLE",
"name": "sales",
"policyTags": {"names": []},
"type": "FLOAT",
},
]
Expand Down