Skip to content

Commit eff365d

Browse files
authored
feat: support data_governance_type (#1708)
* feat: support data_governance_type * remove value validation, add sys test
1 parent 386fa86 commit eff365d

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

google/cloud/bigquery/routine/routine.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Routine(object):
6868
"description": "description",
6969
"determinism_level": "determinismLevel",
7070
"remote_function_options": "remoteFunctionOptions",
71+
"data_governance_type": "dataGovernanceType",
7172
}
7273

7374
def __init__(self, routine_ref, **kwargs) -> None:
@@ -300,8 +301,8 @@ def determinism_level(self, value):
300301

301302
@property
302303
def remote_function_options(self):
303-
"""Optional[google.cloud.bigquery.routine.RemoteFunctionOptions]: Configures remote function
304-
options for a routine.
304+
"""Optional[google.cloud.bigquery.routine.RemoteFunctionOptions]:
305+
Configures remote function options for a routine.
305306
306307
Raises:
307308
ValueError:
@@ -329,6 +330,25 @@ def remote_function_options(self, value):
329330
self._PROPERTY_TO_API_FIELD["remote_function_options"]
330331
] = api_repr
331332

333+
@property
334+
def data_governance_type(self):
335+
"""Optional[str]: If set to ``DATA_MASKING``, the function is validated
336+
and made available as a masking function.
337+
338+
Raises:
339+
ValueError:
340+
If the value is not :data:`string` or :data:`None`.
341+
"""
342+
return self._properties.get(self._PROPERTY_TO_API_FIELD["data_governance_type"])
343+
344+
@data_governance_type.setter
345+
def data_governance_type(self, value):
346+
if value is not None and not isinstance(value, str):
347+
raise ValueError(
348+
"invalid data_governance_type, must be a string or `None`."
349+
)
350+
self._properties[self._PROPERTY_TO_API_FIELD["data_governance_type"]] = value
351+
332352
@classmethod
333353
def from_api_repr(cls, resource: dict) -> "Routine":
334354
"""Factory: construct a routine given its API representation.

tests/system/test_client.py

+36
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import base64
16+
import copy
1617
import csv
1718
import datetime
1819
import decimal
@@ -2236,6 +2237,41 @@ def test_create_tvf_routine(self):
22362237
]
22372238
assert result_rows == expected
22382239

2240+
def test_create_routine_w_data_governance(self):
2241+
routine_name = "routine_with_data_governance"
2242+
dataset = self.temp_dataset(_make_dataset_id("create_routine"))
2243+
2244+
routine = bigquery.Routine(
2245+
dataset.routine(routine_name),
2246+
type_="SCALAR_FUNCTION",
2247+
language="SQL",
2248+
body="x",
2249+
arguments=[
2250+
bigquery.RoutineArgument(
2251+
name="x",
2252+
data_type=bigquery.StandardSqlDataType(
2253+
type_kind=bigquery.StandardSqlTypeNames.INT64
2254+
),
2255+
)
2256+
],
2257+
data_governance_type="DATA_MASKING",
2258+
return_type=bigquery.StandardSqlDataType(
2259+
type_kind=bigquery.StandardSqlTypeNames.INT64
2260+
),
2261+
)
2262+
routine_original = copy.deepcopy(routine)
2263+
2264+
client = Config.CLIENT
2265+
routine_new = client.create_routine(routine)
2266+
2267+
assert routine_new.reference == routine_original.reference
2268+
assert routine_new.type_ == routine_original.type_
2269+
assert routine_new.language == routine_original.language
2270+
assert routine_new.body == routine_original.body
2271+
assert routine_new.arguments == routine_original.arguments
2272+
assert routine_new.return_type == routine_original.return_type
2273+
assert routine_new.data_governance_type == routine_original.data_governance_type
2274+
22392275
def test_create_table_rows_fetch_nested_schema(self):
22402276
table_name = "test_table"
22412277
dataset = self.temp_dataset(_make_dataset_id("create_table_nested_schema"))

tests/unit/routine/test_routine.py

+47
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def test_from_api_repr(target_class):
154154
"foo": "bar",
155155
},
156156
},
157+
"dataGovernanceType": "DATA_MASKING",
157158
}
158159
actual_routine = target_class.from_api_repr(resource)
159160

@@ -192,6 +193,7 @@ def test_from_api_repr(target_class):
192193
assert actual_routine.remote_function_options.connection == "connection_string"
193194
assert actual_routine.remote_function_options.max_batching_rows == 50
194195
assert actual_routine.remote_function_options.user_defined_context == {"foo": "bar"}
196+
assert actual_routine.data_governance_type == "DATA_MASKING"
195197

196198

197199
def test_from_api_repr_tvf_function(target_class):
@@ -294,6 +296,7 @@ def test_from_api_repr_w_minimal_resource(target_class):
294296
assert actual_routine.description is None
295297
assert actual_routine.determinism_level is None
296298
assert actual_routine.remote_function_options is None
299+
assert actual_routine.data_governance_type is None
297300

298301

299302
def test_from_api_repr_w_unknown_fields(target_class):
@@ -428,6 +431,20 @@ def test_from_api_repr_w_unknown_fields(target_class):
428431
"determinismLevel": bigquery.DeterminismLevel.DETERMINISM_LEVEL_UNSPECIFIED
429432
},
430433
),
434+
(
435+
{
436+
"arguments": [{"name": "x", "dataType": {"typeKind": "INT64"}}],
437+
"definitionBody": "x * 3",
438+
"language": "SQL",
439+
"returnType": {"typeKind": "INT64"},
440+
"routineType": "SCALAR_FUNCTION",
441+
"description": "A routine description.",
442+
"determinismLevel": bigquery.DeterminismLevel.DETERMINISM_LEVEL_UNSPECIFIED,
443+
"dataGovernanceType": "DATA_MASKING",
444+
},
445+
["data_governance_type"],
446+
{"dataGovernanceType": "DATA_MASKING"},
447+
),
431448
(
432449
{},
433450
[
@@ -554,6 +571,36 @@ def test_set_remote_function_options_w_none(object_under_test):
554571
assert object_under_test._properties["remoteFunctionOptions"] is None
555572

556573

574+
def test_set_data_governance_type_w_none(object_under_test):
575+
object_under_test.data_governance_type = None
576+
assert object_under_test.data_governance_type is None
577+
assert object_under_test._properties["dataGovernanceType"] is None
578+
579+
580+
def test_set_data_governance_type_valid(object_under_test):
581+
object_under_test.data_governance_type = "DATA_MASKING"
582+
assert object_under_test.data_governance_type == "DATA_MASKING"
583+
assert object_under_test._properties["dataGovernanceType"] == "DATA_MASKING"
584+
585+
586+
def test_set_data_governance_type_wrong_type(object_under_test):
587+
with pytest.raises(ValueError) as exp:
588+
object_under_test.data_governance_type = 1
589+
assert "invalid data_governance_type" in str(exp)
590+
assert object_under_test.data_governance_type is None
591+
assert object_under_test._properties.get("dataGovernanceType") is None
592+
593+
594+
def test_set_data_governance_type_wrong_str(object_under_test):
595+
"""Client does not verify the content of data_governance_type string to be
596+
compatible with future upgrades. If the value is not supported, BigQuery
597+
itself will report an error.
598+
"""
599+
object_under_test.data_governance_type = "RANDOM_STRING"
600+
assert object_under_test.data_governance_type == "RANDOM_STRING"
601+
assert object_under_test._properties["dataGovernanceType"] == "RANDOM_STRING"
602+
603+
557604
def test_repr(target_class):
558605
model = target_class("my-proj.my_dset.my_routine")
559606
actual_routine = repr(model)

0 commit comments

Comments
 (0)