Skip to content

Commit 8a9083f

Browse files
Include star for ref columns (#193)
* initial commit * added test for supporting "*" for reference columns * modified custom sql statement default behavior * removed reference columns from custom sql statement rule * fixed unit tests and linting. * fixed unit tests and linting. * fixed unit tests and linting. * incorporated PR comments * incorporated PR comments * incorporated PR comments and added test for incorrectly specified reference-columns * fixed test pip install
1 parent fbae2d2 commit 8a9083f

27 files changed

Lines changed: 1129 additions & 9 deletions

clouddq/classes/dq_rule_binding.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from clouddq.classes.dq_reference_columns import DqReferenceColumns
2828
from clouddq.classes.dq_row_filter import DqRowFilter
2929
from clouddq.classes.dq_rule import DqRule
30+
from clouddq.integration.bigquery.bigquery_client import BigQueryClient
3031
from clouddq.utils import assert_not_none_or_empty
3132
from clouddq.utils import get_from_dict_and_assert
3233
from clouddq.utils import get_keys_from_dict_and_assert_oneof
@@ -277,6 +278,7 @@ def resolve_reference_columns_config(
277278
def resolve_all_configs_to_dict(
278279
self: DqRuleBinding,
279280
configs_cache: dq_configs_cache.DqConfigsCache,
281+
bigquery_client: BigQueryClient,
280282
) -> dict:
281283
"""
282284
@@ -334,11 +336,27 @@ def resolve_all_configs_to_dict(
334336
row_filter_config = self.resolve_row_filter_config(configs_cache)
335337
# resolve reference columns config
336338
if self.reference_columns_id:
339+
include_all_reference_columns = False
337340
include_reference_columns = self.resolve_reference_columns_config(
338341
configs_cache
339342
).include_reference_columns
343+
if "*" in include_reference_columns:
344+
if len(include_reference_columns) == 1:
345+
column_names = bigquery_client.get_table_columns(
346+
table=table_entity.get_table_name(),
347+
project_id=table_entity.instance_name,
348+
)
349+
include_reference_columns = sorted(column_names)
350+
include_all_reference_columns = True
351+
elif len(include_reference_columns) > 1:
352+
raise ValueError(
353+
f"The specified reference columns id {self.reference_columns_id} \n"
354+
f"must contain only '*' or column names but not both.\n "
355+
f"Current reference columns configs contains \n {include_reference_columns} columns"
356+
)
340357
else:
341358
include_reference_columns = []
359+
include_all_reference_columns = False
342360

343361
return dict(
344362
{
@@ -348,6 +366,7 @@ def resolve_all_configs_to_dict(
348366
"column_id": self.column_id,
349367
"reference_columns_id": self.reference_columns_id,
350368
"include_reference_columns": include_reference_columns,
369+
"include_all_reference_columns": include_all_reference_columns,
351370
"column_configs": dict(column_configs.dict_values()),
352371
"rule_ids": list(self.rule_ids),
353372
"rule_configs_dict": rule_configs_dict,

clouddq/integration/bigquery/bigquery_client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,16 @@ def get_table_schema(self, table: str, project_id: str = None) -> dict:
269269

270270
logger.debug(f"Schema for table {table} is: {columns_dict}")
271271
return columns_dict
272+
273+
def get_table_columns(self, table: str, project_id: str = None) -> set:
274+
275+
client = self.get_connection(project_id=project_id)
276+
try:
277+
table_ref = client.get_table(table)
278+
except KeyError as error:
279+
raise KeyError(f"\n\nInput table `{table}` is not valid.\n{error}")
280+
281+
column_names = {column.name for column in table_ref.schema}
282+
logger.debug(f"List of columns in table {table} is: {column_names}")
283+
284+
return column_names

clouddq/lib.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ def prepare_configs_from_rule_binding_id(
207207
)
208208
resolved_rule_binding_configs = rule_binding.resolve_all_configs_to_dict(
209209
configs_cache=configs_cache,
210+
bigquery_client=bigquery_client,
210211
)
211212
configs: dict[typing.Any, typing.Any] = {
212213
"configs": dict(resolved_rule_binding_configs)
@@ -285,7 +286,7 @@ def get_high_watermark_value(
285286
AND rule_binding_id = '{rule_binding_id}'
286287
AND progress_watermark IS TRUE ;"""
287288
else:
288-
query = f"""SELECT
289+
query = """SELECT
289290
TIMESTAMP("1970-01-01 00:00:00") as high_watermark,
290291
CURRENT_TIMESTAMP() as current_timestamp_value ;"""
291292
logger.info(f"High watermark query is \n {query}")

configs/reference_columns/reference-columns.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ reference_columns:
1919
- row_id
2020
- contact_type
2121
- value
22+
23+
INCLUDE_ALL_REFERENCE_COLUMNS:
24+
include_reference_columns:
25+
- *

tests/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,11 @@ py_test(
238238
legacy_create_init = 0,
239239
deps = DEPS,
240240
)
241+
242+
py_test(
243+
name = "test_reference_columns",
244+
srcs = SRCS,
245+
data = DATA,
246+
legacy_create_init = 0,
247+
deps = DEPS,
248+
)

tests/conftest.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ def source_dq_rules_configs_file_path():
273273
def source_dq_advanced_rules_configs_path():
274274
return Path("docs").joinpath("examples").joinpath("advanced_rules").absolute()
275275

276+
@pytest.fixture(scope="session")
277+
def source_dq_reference_configs_file_path():
278+
return Path("tests").joinpath("resources").joinpath("dq_reference_configs.yml").absolute()
279+
276280
@pytest.fixture(scope="session")
277281
def test_profiles_dir():
278282
return Path("tests").joinpath("resources", "test_dbt_profiles_dir").absolute()
@@ -396,6 +400,7 @@ def temp_configs_dir(
396400
lines = lines.replace("<my-gcp-dataplex-zone-id>", gcp_dataplex_zone_id)
397401
lines = lines.replace("<my_bigquery_dataset_id>", gcp_dataplex_bigquery_dataset_id)
398402
source_file.write(lines)
403+
399404
yield configs_path.absolute()
400405
if os.path.exists(temp_clouddq_dir):
401406
shutil.rmtree(temp_clouddq_dir)
@@ -476,6 +481,33 @@ def temp_configs_from_dq_rules_config_file(
476481
if os.path.exists(temp_clouddq_dir):
477482
os.unlink(temp_clouddq_dir)
478483

484+
@pytest.fixture(scope="function")
485+
def temp_configs_from_dq_reference_configs_file(
486+
gcp_project_id,
487+
gcp_dataplex_bigquery_dataset_id,
488+
gcp_dataplex_region,
489+
gcp_dataplex_lake_name,
490+
gcp_dataplex_zone_id,
491+
source_dq_reference_configs_file_path,
492+
tmp_path):
493+
# Create temp directory
494+
temp_clouddq_dir = Path(tmp_path).joinpath("clouddq_test_dq_rules_configs")
495+
# Copy over tests/resources/dq_reference_configs.yaml
496+
registry_defaults = shutil.copyfile(source_dq_reference_configs_file_path, temp_clouddq_dir)
497+
# Prepare entity_uri configs
498+
with open(registry_defaults) as source_file:
499+
lines = source_file.read()
500+
with open(registry_defaults, "w") as source_file:
501+
lines = lines.replace("<my-gcp-dataplex-lake-id>", gcp_dataplex_lake_name)
502+
lines = lines.replace("<my-gcp-dataplex-region-id>", gcp_dataplex_region)
503+
lines = lines.replace("<my-gcp-project-id>", gcp_project_id)
504+
lines = lines.replace("<my-gcp-dataplex-zone-id>", gcp_dataplex_zone_id)
505+
lines = lines.replace("<my_bigquery_dataset_id>", gcp_dataplex_bigquery_dataset_id)
506+
source_file.write(lines)
507+
yield temp_clouddq_dir.absolute()
508+
if os.path.exists(temp_clouddq_dir):
509+
os.unlink(temp_clouddq_dir)
510+
479511
@pytest.fixture(scope="function")
480512
def temp_configs_from_dq_advanced_rules_configs(
481513
gcp_project_id,

tests/integration/test_bigquery_client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,31 @@ def test_get_table_schema_with_default_partitioning(self,
194194
else:
195195
print(f'`{table_name}` does not exists in the project `{project_id}`')
196196

197+
def test_get_table_columns(self,
198+
gcp_project_id,
199+
test_bigquery_client):
200+
table_name = "bigquery-public-data.github_repos.commits"
201+
table_ref = test_bigquery_client.table_from_string(
202+
table_name
203+
)
204+
project_id = table_ref.project
205+
is_table_exists = test_bigquery_client.is_table_exists(
206+
table=table_name,
207+
project_id=project_id
208+
)
209+
if is_table_exists:
210+
table_columns = test_bigquery_client.get_table_columns(
211+
table=table_name,
212+
project_id=project_id
213+
)
214+
expected_table_columns = {
215+
'commit', 'tree', 'parent', 'author', 'committer', 'subject', 'message',
216+
'trailer', 'difference', 'difference_truncated', 'repo_name', 'encoding'
217+
}
218+
assert table_columns == expected_table_columns
219+
else:
220+
print(f'`{table_name}` does not exists in the project `{project_id}`')
221+
197222

198223
if __name__ == "__main__":
199224
raise SystemExit(pytest.main([__file__, '-vv', '-rP', '-n', 'auto']))

tests/integration/test_dataplex_metadata_uri_templates.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def test_rule_bindings_class_resolve_configs(
8585
test_rule_bindings_collection_team_4,
8686
test_default_dataplex_configs_cache,
8787
test_dataplex_metadata_defaults_configs,
88+
test_bigquery_client,
8889
):
8990
for key, value in test_rule_bindings_collection_team_4.items():
9091
rule_binding = DqRuleBinding.from_dict(
@@ -94,7 +95,8 @@ def test_rule_bindings_class_resolve_configs(
9495
rule_binding.resolve_table_entity_config(configs_cache=test_default_dataplex_configs_cache)
9596
rule_binding.resolve_rule_config_list(configs_cache=test_default_dataplex_configs_cache)
9697
rule_binding.resolve_row_filter_config(configs_cache=test_default_dataplex_configs_cache)
97-
rule_binding.resolve_all_configs_to_dict(configs_cache=test_default_dataplex_configs_cache)
98+
rule_binding.resolve_all_configs_to_dict(configs_cache=test_default_dataplex_configs_cache,
99+
bigquery_client=test_bigquery_client)
98100

99101
def test_prepare_configs_from_rule_binding(
100102
self,
@@ -199,6 +201,7 @@ def test_rule_bindings_class_resolve_configs_from_file(
199201
test_default_dataplex_configs_cache_from_file,
200202
test_dataplex_metadata_defaults_configs,
201203
test_rule_bindings_collection_from_configs_file,
204+
test_bigquery_client,
202205
):
203206
for rule_binding_id, rule_binding_configs in test_rule_bindings_collection_from_configs_file.items():
204207
rule_binding = DqRuleBinding.from_dict(
@@ -208,7 +211,8 @@ def test_rule_bindings_class_resolve_configs_from_file(
208211
rule_binding.resolve_table_entity_config(configs_cache=test_default_dataplex_configs_cache_from_file)
209212
rule_binding.resolve_rule_config_list(configs_cache=test_default_dataplex_configs_cache_from_file)
210213
rule_binding.resolve_row_filter_config(configs_cache=test_default_dataplex_configs_cache_from_file)
211-
rule_binding.resolve_all_configs_to_dict(configs_cache=test_default_dataplex_configs_cache_from_file)
214+
rule_binding.resolve_all_configs_to_dict(configs_cache=test_default_dataplex_configs_cache_from_file,
215+
bigquery_client=test_bigquery_client)
212216

213217
def test_prepare_configs_from_configs_file(
214218
self,
@@ -313,6 +317,7 @@ def test_rule_bindings_class_resolve_gcs_configs(
313317
test_rule_bindings_collection_team_5,
314318
test_default_dataplex_configs_cache,
315319
test_dataplex_metadata_defaults_configs,
320+
test_bigquery_client,
316321
):
317322
for key, value in test_rule_bindings_collection_team_5.items():
318323
rule_binding = DqRuleBinding.from_dict(
@@ -322,7 +327,8 @@ def test_rule_bindings_class_resolve_gcs_configs(
322327
rule_binding.resolve_table_entity_config(configs_cache=test_default_dataplex_configs_cache)
323328
rule_binding.resolve_rule_config_list(configs_cache=test_default_dataplex_configs_cache)
324329
rule_binding.resolve_row_filter_config(configs_cache=test_default_dataplex_configs_cache)
325-
rule_binding.resolve_all_configs_to_dict(configs_cache=test_default_dataplex_configs_cache)
330+
rule_binding.resolve_all_configs_to_dict(configs_cache=test_default_dataplex_configs_cache,
331+
bigquery_client=test_bigquery_client)
326332

327333
def test_prepare_configs_from_gcs_rule_binding(
328334
self,

0 commit comments

Comments
 (0)