google-pso-data-validator 7.2.0__tar.gz → 7.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/PKG-INFO +1 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/__main__.py +0 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/combiner.py +135 -48
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/consts.py +25 -14
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/data_validation.py +6 -3
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/result_handlers/bigquery.py +1 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/secret_manager.py +6 -2
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/util.py +18 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/PKG-INFO +1 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/setup.py +1 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/__init__.py +16 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_snowflake/api.py +4 -1
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/__init__.py +3 -10
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/api.py +5 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/LICENSE +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/README.md +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/app.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/cli_tools.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/client_info.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/clients.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/config_manager.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/exceptions.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/find_tables.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/gcs_helper.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/jellyfish_distance.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/metadata.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/partition_builder.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/partition_row_builder.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/query_builder.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/random_row_builder.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/raw_query.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/result_handlers/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/result_handlers/text.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/schema_validation.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/state_manager.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/validation_builder.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/requires.txt +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/setup.cfg +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_addon/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_addon/operations.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_biquery/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_biquery/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/registry.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_impala/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/client.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/registry.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_redshift/api.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
- {google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/__main__.py
RENAMED
|
@@ -570,7 +570,6 @@ def run_validations(args, config_managers):
|
|
|
570
570
|
Args:
|
|
571
571
|
config_managers (list[ConfigManager]): List of config manager instances.
|
|
572
572
|
"""
|
|
573
|
-
# TODO(issue/31): Add parallel execution logic
|
|
574
573
|
for config_manager in config_managers:
|
|
575
574
|
run_validation(config_manager, dry_run=args.dry_run, verbose=args.verbose)
|
|
576
575
|
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/combiner.py
RENAMED
|
@@ -25,17 +25,20 @@ import logging
|
|
|
25
25
|
import ibis
|
|
26
26
|
import ibis.expr.datatypes as dt
|
|
27
27
|
|
|
28
|
+
from typing import TYPE_CHECKING
|
|
28
29
|
from data_validation import consts
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pandas import DataFrame
|
|
33
|
+
import ibis.expr.types.relations.table as IbisTable
|
|
34
|
+
from data_validation.metadata import RunMetadata, ValidationMetadata
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
def generate_report(
|
|
35
38
|
client,
|
|
36
|
-
run_metadata,
|
|
37
|
-
source,
|
|
38
|
-
target,
|
|
39
|
+
run_metadata: "RunMetadata",
|
|
40
|
+
source: "IbisTable",
|
|
41
|
+
target: "IbisTable",
|
|
39
42
|
join_on_fields=(),
|
|
40
43
|
is_value_comparison=False,
|
|
41
44
|
verbose=False,
|
|
@@ -44,8 +47,7 @@ def generate_report(
|
|
|
44
47
|
|
|
45
48
|
Args:
|
|
46
49
|
client (ibis.client.Client): Ibis client used to combine results.
|
|
47
|
-
run_metadata
|
|
48
|
-
Metadata about the run and validations.
|
|
50
|
+
run_metadata: Metadata about the run and validations.
|
|
49
51
|
source (ibis.QUERY): Ibis query / table object.
|
|
50
52
|
target (ibis.QUERY): Ibis query / table object.
|
|
51
53
|
join_on_fields (Sequence[str]):
|
|
@@ -69,7 +71,7 @@ def generate_report(
|
|
|
69
71
|
if source_names != target_names:
|
|
70
72
|
raise ValueError(
|
|
71
73
|
"Expected source and target to have same schema, got "
|
|
72
|
-
f"
|
|
74
|
+
f"{consts.RESULT_TYPE_SOURCE}: {source_names}; {consts.RESULT_TYPE_TARGET}: {target_names}"
|
|
73
75
|
)
|
|
74
76
|
differences_pivot = _calculate_differences(
|
|
75
77
|
source, target, join_on_fields, run_metadata.validations, is_value_comparison
|
|
@@ -87,13 +89,17 @@ def generate_report(
|
|
|
87
89
|
target_df = client.execute(target_pivot)
|
|
88
90
|
|
|
89
91
|
con = ibis.pandas.connect(
|
|
90
|
-
{
|
|
92
|
+
{
|
|
93
|
+
consts.RESULT_TYPE_SOURCE: source_df,
|
|
94
|
+
"differences": differences_df,
|
|
95
|
+
consts.RESULT_TYPE_TARGET: target_df,
|
|
96
|
+
}
|
|
91
97
|
)
|
|
92
98
|
joined = _join_pivots(
|
|
93
99
|
con.tables.source, con.tables.target, con.tables.differences, join_on_fields
|
|
94
100
|
)
|
|
95
101
|
|
|
96
|
-
documented = _add_metadata(joined, run_metadata)
|
|
102
|
+
documented, run_metadata = _add_metadata(joined, run_metadata)
|
|
97
103
|
|
|
98
104
|
if verbose:
|
|
99
105
|
logging.debug("-- ** Combiner Query ** --")
|
|
@@ -112,15 +118,17 @@ def generate_report(
|
|
|
112
118
|
first.get_table_name(consts.RESULT_TYPE_TARGET), inplace=True
|
|
113
119
|
)
|
|
114
120
|
|
|
121
|
+
_get_summary(run_metadata, result_df, source_df, target_df)
|
|
122
|
+
|
|
115
123
|
return result_df
|
|
116
124
|
|
|
117
125
|
|
|
118
126
|
def _calculate_difference(
|
|
119
|
-
field_differences,
|
|
127
|
+
field_differences: "IbisTable",
|
|
120
128
|
datatype: dt.DataType,
|
|
121
129
|
target_type: dt.DataType,
|
|
122
|
-
validation,
|
|
123
|
-
is_value_comparison,
|
|
130
|
+
validation: "ValidationMetadata",
|
|
131
|
+
is_value_comparison: bool,
|
|
124
132
|
):
|
|
125
133
|
pct_threshold = ibis.literal(validation.threshold)
|
|
126
134
|
if datatype.is_timestamp() or datatype.is_date():
|
|
@@ -210,12 +218,16 @@ def _calculate_difference(
|
|
|
210
218
|
difference.name("difference"),
|
|
211
219
|
pct_difference.name("pct_difference"),
|
|
212
220
|
pct_threshold.name("pct_threshold"),
|
|
213
|
-
validation_status.name(
|
|
221
|
+
validation_status.name(consts.VALIDATION_STATUS),
|
|
214
222
|
)
|
|
215
223
|
|
|
216
224
|
|
|
217
225
|
def _calculate_differences(
|
|
218
|
-
source
|
|
226
|
+
source: "IbisTable",
|
|
227
|
+
target: "IbisTable",
|
|
228
|
+
join_on_fields: tuple,
|
|
229
|
+
validations: "dict[ValidationMetadata]",
|
|
230
|
+
is_value_comparison: bool,
|
|
219
231
|
):
|
|
220
232
|
"""Calculate differences between source and target fields.
|
|
221
233
|
|
|
@@ -267,7 +279,12 @@ def _calculate_differences(
|
|
|
267
279
|
return differences_pivot
|
|
268
280
|
|
|
269
281
|
|
|
270
|
-
def _pivot_result(
|
|
282
|
+
def _pivot_result(
|
|
283
|
+
result: "IbisTable",
|
|
284
|
+
join_on_fields: tuple,
|
|
285
|
+
validations: "dict[ValidationMetadata]",
|
|
286
|
+
result_type: str,
|
|
287
|
+
):
|
|
271
288
|
all_fields = frozenset(result.schema().names)
|
|
272
289
|
validation_fields = (
|
|
273
290
|
all_fields - frozenset(join_on_fields)
|
|
@@ -285,19 +302,21 @@ def _pivot_result(result, join_on_fields, validations, result_type):
|
|
|
285
302
|
ibis.literal("{")
|
|
286
303
|
+ ibis.literal(", ").join(validation.primary_keys)
|
|
287
304
|
+ ibis.literal("}")
|
|
288
|
-
).name(
|
|
305
|
+
).name(consts.CONFIG_PRIMARY_KEYS)
|
|
289
306
|
else:
|
|
290
|
-
primary_keys =
|
|
307
|
+
primary_keys = (
|
|
308
|
+
ibis.literal(None).cast("string").name(consts.CONFIG_PRIMARY_KEYS)
|
|
309
|
+
)
|
|
291
310
|
|
|
292
311
|
pivots.append(
|
|
293
312
|
result.projection(
|
|
294
313
|
(
|
|
295
314
|
ibis.literal(field).name("validation_name"),
|
|
296
315
|
ibis.literal(validation.validation_type).name(
|
|
297
|
-
|
|
316
|
+
consts.VALIDATION_TYPE
|
|
298
317
|
),
|
|
299
318
|
ibis.literal(validation.aggregation_type).name(
|
|
300
|
-
|
|
319
|
+
consts.AGGREGATION_TYPE
|
|
301
320
|
),
|
|
302
321
|
ibis.literal(validation.get_table_name(result_type)).name(
|
|
303
322
|
"table_name"
|
|
@@ -333,7 +352,12 @@ def _as_json(expr):
|
|
|
333
352
|
)
|
|
334
353
|
|
|
335
354
|
|
|
336
|
-
def _join_pivots(
|
|
355
|
+
def _join_pivots(
|
|
356
|
+
source: "IbisTable",
|
|
357
|
+
target: "IbisTable",
|
|
358
|
+
differences: "IbisTable",
|
|
359
|
+
join_on_fields: tuple,
|
|
360
|
+
):
|
|
337
361
|
if join_on_fields:
|
|
338
362
|
join_values = []
|
|
339
363
|
for field in join_on_fields:
|
|
@@ -346,62 +370,125 @@ def _join_pivots(source, target, differences, join_on_fields):
|
|
|
346
370
|
|
|
347
371
|
group_by_columns = (
|
|
348
372
|
ibis.literal("{") + ibis.literal(", ").join(join_values) + ibis.literal("}")
|
|
349
|
-
).name(
|
|
373
|
+
).name(consts.GROUP_BY_COLUMNS)
|
|
350
374
|
else:
|
|
351
|
-
group_by_columns =
|
|
375
|
+
group_by_columns = (
|
|
376
|
+
ibis.literal(None).cast("string").name(consts.GROUP_BY_COLUMNS)
|
|
377
|
+
)
|
|
352
378
|
|
|
353
379
|
join_keys = ("validation_name",) + join_on_fields
|
|
354
380
|
source_difference = source.join(differences, join_keys, how="outer")[
|
|
355
381
|
[source[field] for field in join_keys]
|
|
356
382
|
+ [
|
|
357
|
-
source[
|
|
358
|
-
source[
|
|
359
|
-
source[
|
|
383
|
+
source[consts.VALIDATION_TYPE],
|
|
384
|
+
source[consts.AGGREGATION_TYPE],
|
|
385
|
+
source[consts.CONFIG_TABLE_NAME],
|
|
360
386
|
source["column_name"],
|
|
361
|
-
source[
|
|
387
|
+
source[consts.CONFIG_PRIMARY_KEYS],
|
|
362
388
|
source["num_random_rows"],
|
|
363
389
|
source["agg_value"],
|
|
364
390
|
differences["difference"],
|
|
365
391
|
differences["pct_difference"],
|
|
366
392
|
differences["pct_threshold"],
|
|
367
|
-
differences[
|
|
393
|
+
differences[consts.VALIDATION_STATUS],
|
|
368
394
|
]
|
|
369
395
|
]
|
|
370
396
|
joined = source_difference.join(target, join_keys, how="outer")[
|
|
371
397
|
source_difference["validation_name"],
|
|
372
|
-
source_difference[
|
|
373
|
-
.fillna(target[
|
|
374
|
-
.name(
|
|
375
|
-
source_difference[
|
|
376
|
-
.fillna(target[
|
|
377
|
-
.name(
|
|
378
|
-
source_difference["table_name"].name(
|
|
379
|
-
source_difference["column_name"].name(
|
|
380
|
-
source_difference["agg_value"].name(
|
|
381
|
-
target["table_name"].name(
|
|
382
|
-
target["column_name"].name(
|
|
383
|
-
target["agg_value"].name(
|
|
398
|
+
source_difference[consts.VALIDATION_TYPE]
|
|
399
|
+
.fillna(target[consts.VALIDATION_TYPE])
|
|
400
|
+
.name(consts.VALIDATION_TYPE),
|
|
401
|
+
source_difference[consts.AGGREGATION_TYPE]
|
|
402
|
+
.fillna(target[consts.AGGREGATION_TYPE])
|
|
403
|
+
.name(consts.AGGREGATION_TYPE),
|
|
404
|
+
source_difference["table_name"].name(consts.SOURCE_TABLE_NAME),
|
|
405
|
+
source_difference["column_name"].name(consts.SOURCE_COLUMN_NAME),
|
|
406
|
+
source_difference["agg_value"].name(consts.SOURCE_AGG_VALUE),
|
|
407
|
+
target["table_name"].name(consts.TARGET_TABLE_NAME),
|
|
408
|
+
target["column_name"].name(consts.TARGET_COLUMN_NAME),
|
|
409
|
+
target["agg_value"].name(consts.TARGET_AGG_VALUE),
|
|
384
410
|
group_by_columns,
|
|
385
|
-
source_difference[
|
|
411
|
+
source_difference[consts.CONFIG_PRIMARY_KEYS],
|
|
386
412
|
source_difference["num_random_rows"],
|
|
387
413
|
source_difference["difference"],
|
|
388
414
|
source_difference["pct_difference"],
|
|
389
415
|
source_difference["pct_threshold"],
|
|
390
|
-
source_difference[
|
|
416
|
+
source_difference[consts.VALIDATION_STATUS],
|
|
391
417
|
]
|
|
392
418
|
return joined
|
|
393
419
|
|
|
394
420
|
|
|
395
|
-
def _add_metadata(joined, run_metadata):
|
|
421
|
+
def _add_metadata(joined: "IbisTable", run_metadata: "RunMetadata"):
|
|
396
422
|
# TODO: Add source and target queries to metadata
|
|
397
423
|
run_metadata.end_time = datetime.datetime.now(datetime.timezone.utc)
|
|
398
424
|
|
|
399
425
|
joined = joined[
|
|
400
426
|
joined,
|
|
401
|
-
ibis.literal(run_metadata.run_id).name(
|
|
402
|
-
ibis.literal(run_metadata.labels).name(
|
|
403
|
-
ibis.literal(run_metadata.start_time).name(
|
|
404
|
-
ibis.literal(run_metadata.end_time).name(
|
|
427
|
+
ibis.literal(run_metadata.run_id).name(consts.CONFIG_RUN_ID),
|
|
428
|
+
ibis.literal(run_metadata.labels).name(consts.CONFIG_LABELS),
|
|
429
|
+
ibis.literal(run_metadata.start_time).name(consts.CONFIG_START_TIME),
|
|
430
|
+
ibis.literal(run_metadata.end_time).name(consts.CONFIG_END_TIME),
|
|
405
431
|
]
|
|
406
432
|
|
|
407
|
-
return joined
|
|
433
|
+
return (joined, run_metadata)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _get_summary(
|
|
437
|
+
run_metadata: "RunMetadata",
|
|
438
|
+
result_df: "DataFrame",
|
|
439
|
+
source_df: "DataFrame",
|
|
440
|
+
target_df: "DataFrame",
|
|
441
|
+
):
|
|
442
|
+
"""Logs a summary report/stats of row validation results."""
|
|
443
|
+
try:
|
|
444
|
+
if result_df.loc[0, consts.VALIDATION_TYPE] == consts.ROW_VALIDATION:
|
|
445
|
+
# Vectorized calculations for all counts
|
|
446
|
+
success_condition = (
|
|
447
|
+
result_df[consts.VALIDATION_STATUS] == consts.VALIDATION_STATUS_SUCCESS
|
|
448
|
+
)
|
|
449
|
+
fail_condition = ~success_condition # Invert success for fail condition
|
|
450
|
+
|
|
451
|
+
source_not_in_target = (
|
|
452
|
+
result_df[consts.SOURCE_AGG_VALUE].notnull()
|
|
453
|
+
& result_df[consts.TARGET_AGG_VALUE].isnull()
|
|
454
|
+
)
|
|
455
|
+
target_not_in_source = (
|
|
456
|
+
result_df[consts.SOURCE_AGG_VALUE].isnull()
|
|
457
|
+
& result_df[consts.TARGET_AGG_VALUE].notnull()
|
|
458
|
+
)
|
|
459
|
+
present_in_both_tables = (
|
|
460
|
+
result_df[consts.SOURCE_AGG_VALUE].notnull()
|
|
461
|
+
& result_df[consts.TARGET_AGG_VALUE].notnull()
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
logging.info(
|
|
465
|
+
{
|
|
466
|
+
consts.CONFIG_RUN_ID: run_metadata.run_id,
|
|
467
|
+
consts.CONFIG_START_TIME: run_metadata.start_time.strftime(
|
|
468
|
+
"%Y-%m-%d %H:%M:%S %Z"
|
|
469
|
+
),
|
|
470
|
+
consts.CONFIG_END_TIME: run_metadata.end_time.strftime(
|
|
471
|
+
"%Y-%m-%d %H:%M:%S %Z"
|
|
472
|
+
),
|
|
473
|
+
consts.TOTAL_SOURCE_ROWS: source_df.shape[0],
|
|
474
|
+
consts.TOTAL_TARGET_ROWS: target_df.shape[0],
|
|
475
|
+
consts.TOTAL_ROWS_VALIDATED: result_df.shape[0],
|
|
476
|
+
# Using .sum() on boolean Series for much faster counting
|
|
477
|
+
consts.TOTAL_ROWS_SUCCESS: success_condition.sum(),
|
|
478
|
+
consts.TOTAL_ROWS_FAIL: fail_condition.sum(),
|
|
479
|
+
consts.FAILED_SOURCE_NOT_IN_TARGET: (
|
|
480
|
+
fail_condition & source_not_in_target
|
|
481
|
+
).sum(),
|
|
482
|
+
consts.FAILED_TARGET_NOT_IN_SOURCE: (
|
|
483
|
+
fail_condition & target_not_in_source
|
|
484
|
+
).sum(),
|
|
485
|
+
consts.FAILED_PRESENT_IN_BOTH_TABLES: (
|
|
486
|
+
fail_condition & present_in_both_tables
|
|
487
|
+
).sum(),
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
except Exception as e:
|
|
491
|
+
logging.warning(
|
|
492
|
+
f"Error while generating summary report of row validation results: {e}",
|
|
493
|
+
exc_info=True,
|
|
494
|
+
)
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/consts.py
RENAMED
|
@@ -56,6 +56,8 @@ CONFIG_CASE_INSENSITIVE_MATCH = "case_insensitive_match"
|
|
|
56
56
|
CONFIG_ROW_CONCAT = "concat"
|
|
57
57
|
CONFIG_ROW_HASH = "hash"
|
|
58
58
|
CONFIG_RUN_ID = "run_id"
|
|
59
|
+
CONFIG_START_TIME = "start_time"
|
|
60
|
+
CONFIG_END_TIME = "end_time"
|
|
59
61
|
CONFIG_SOURCE_COLUMN = "source_column"
|
|
60
62
|
CONFIG_TARGET_COLUMN = "target_column"
|
|
61
63
|
CONFIG_THRESHOLD = "threshold"
|
|
@@ -142,12 +144,21 @@ TARGET_AGG_VALUE = "target_agg_value"
|
|
|
142
144
|
VALIDATION_STATUS = "validation_status"
|
|
143
145
|
VALIDATION_STATUS_SUCCESS = "success"
|
|
144
146
|
VALIDATION_STATUS_FAIL = "fail"
|
|
145
|
-
# TODO: update if we start to support other statuses
|
|
146
147
|
VALIDATION_STATUSES = [
|
|
147
148
|
VALIDATION_STATUS_SUCCESS,
|
|
148
149
|
VALIDATION_STATUS_FAIL,
|
|
149
150
|
]
|
|
150
151
|
|
|
152
|
+
# Summary stats of Row Validation results
|
|
153
|
+
TOTAL_SOURCE_ROWS = "total_source_rows"
|
|
154
|
+
TOTAL_TARGET_ROWS = "total_target_rows"
|
|
155
|
+
TOTAL_ROWS_VALIDATED = "total_rows_validated"
|
|
156
|
+
TOTAL_ROWS_SUCCESS = "total_rows_success_validation_status"
|
|
157
|
+
TOTAL_ROWS_FAIL = "total_rows_fail_validation_status"
|
|
158
|
+
FAILED_SOURCE_NOT_IN_TARGET = "failed_rows_present_in_source_not_in_target"
|
|
159
|
+
FAILED_TARGET_NOT_IN_SOURCE = "failed_rows_present_in_target_not_in_source"
|
|
160
|
+
FAILED_PRESENT_IN_BOTH_TABLES = "failed_rows_present_in_both_source_and_target"
|
|
161
|
+
|
|
151
162
|
# SQL Template Formatting
|
|
152
163
|
# TODO: should this be managed in query_builder if that is the only place its used?
|
|
153
164
|
COUNT_STAR = "{count_star}"
|
|
@@ -160,25 +171,25 @@ FORMAT_TYPES = ["csv", "json", "table", "text"]
|
|
|
160
171
|
|
|
161
172
|
# Text Result Handler column filter list
|
|
162
173
|
COLUMN_FILTER_LIST = [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
AGGREGATION_TYPE,
|
|
175
|
+
CONFIG_END_TIME,
|
|
176
|
+
CONFIG_LABELS,
|
|
166
177
|
"pct_threshold",
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
CONFIG_START_TIME,
|
|
179
|
+
TARGET_TABLE_NAME,
|
|
180
|
+
TARGET_COLUMN_NAME,
|
|
170
181
|
"difference",
|
|
171
|
-
|
|
172
|
-
|
|
182
|
+
CONFIG_PRIMARY_KEYS,
|
|
183
|
+
GROUP_BY_COLUMNS,
|
|
173
184
|
"num_random_rows",
|
|
174
185
|
]
|
|
175
186
|
SCHEMA_VALIDATION_COLUMN_FILTER_LIST = [
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
CONFIG_START_TIME,
|
|
188
|
+
CONFIG_END_TIME,
|
|
189
|
+
AGGREGATION_TYPE,
|
|
179
190
|
"difference",
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
CONFIG_PRIMARY_KEYS,
|
|
192
|
+
GROUP_BY_COLUMNS,
|
|
182
193
|
"num_random_rows",
|
|
183
194
|
"pct_threshold",
|
|
184
195
|
]
|
|
@@ -353,7 +353,10 @@ class DataValidation(object):
|
|
|
353
353
|
target_df = futures[1].result()
|
|
354
354
|
|
|
355
355
|
pandas_client = ibis.pandas.connect(
|
|
356
|
-
{
|
|
356
|
+
{
|
|
357
|
+
consts.RESULT_TYPE_SOURCE: source_df,
|
|
358
|
+
consts.RESULT_TYPE_TARGET: target_df,
|
|
359
|
+
}
|
|
357
360
|
)
|
|
358
361
|
|
|
359
362
|
try:
|
|
@@ -362,8 +365,8 @@ class DataValidation(object):
|
|
|
362
365
|
combiner.generate_report,
|
|
363
366
|
pandas_client,
|
|
364
367
|
self.run_metadata,
|
|
365
|
-
pandas_client.table(
|
|
366
|
-
pandas_client.table(
|
|
368
|
+
pandas_client.table(consts.RESULT_TYPE_SOURCE),
|
|
369
|
+
pandas_client.table(consts.RESULT_TYPE_TARGET),
|
|
367
370
|
join_on_fields=join_on_fields,
|
|
368
371
|
is_value_comparison=is_value_comparison,
|
|
369
372
|
verbose=self.verbose,
|
|
@@ -113,7 +113,7 @@ class BigQueryResultHandler(object):
|
|
|
113
113
|
else:
|
|
114
114
|
logging.info(f'{BQRH_WRITE_MESSAGE}, run id: {result_df.iloc[0]["run_id"]}')
|
|
115
115
|
|
|
116
|
-
# Handler also
|
|
116
|
+
# Handler can also output results after saving to BigQuery.
|
|
117
117
|
logger = logging.getLogger()
|
|
118
118
|
if logger.isEnabledFor(logging.DEBUG):
|
|
119
119
|
# Checking log level to avoid evaluating a large Dataframe that will never be logged.
|
|
@@ -53,6 +53,10 @@ class GCPSecretManager:
|
|
|
53
53
|
# Return the decoded payload.
|
|
54
54
|
payload = response.payload.data.decode("UTF-8")
|
|
55
55
|
return payload
|
|
56
|
-
except (
|
|
57
|
-
|
|
56
|
+
except (
|
|
57
|
+
exceptions.NotFound,
|
|
58
|
+
exceptions.InvalidArgument,
|
|
59
|
+
exceptions.PermissionDenied,
|
|
60
|
+
):
|
|
61
|
+
# The secret does not exist or is inaccessible and therefore we can assume it is just a token to be returned.
|
|
58
62
|
return secret_id
|
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import json
|
|
15
16
|
import logging
|
|
16
17
|
import re
|
|
17
18
|
import time
|
|
18
19
|
|
|
20
|
+
from data_validation import exceptions
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
def timed_call(log_txt, fn, *args, **kwargs):
|
|
21
24
|
t0 = time.time()
|
|
@@ -44,3 +47,18 @@ def split_not_in_quotes(
|
|
|
44
47
|
return [t for t in re.split(pattern, to_split) if t]
|
|
45
48
|
else:
|
|
46
49
|
return re.split(pattern, to_split)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def dvt_config_string_to_dict(config_string: str) -> dict:
|
|
53
|
+
"""Convert JSON in a string to a dict."""
|
|
54
|
+
if not config_string:
|
|
55
|
+
return None
|
|
56
|
+
if isinstance(config_string, dict):
|
|
57
|
+
return config_string
|
|
58
|
+
try:
|
|
59
|
+
param_dict = json.loads(config_string.replace("'", '"'))
|
|
60
|
+
return param_dict
|
|
61
|
+
except json.JSONDecodeError as exc:
|
|
62
|
+
raise exceptions.ValidationException(
|
|
63
|
+
f"Invalid JSON format in connection parameter dictionary string: {config_string}"
|
|
64
|
+
) from exc
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import sqlalchemy as sa
|
|
15
|
-
from sqlalchemy.dialects.oracle.base import
|
|
15
|
+
from sqlalchemy.dialects.oracle.base import (
|
|
16
|
+
OracleIdentifierPreparer,
|
|
17
|
+
RESERVED_WORDS as ORACLE_RESERVED_WORDS,
|
|
18
|
+
)
|
|
16
19
|
from sqlalchemy.dialects.oracle.cx_oracle import OracleDialect_cx_oracle
|
|
17
20
|
import re
|
|
18
21
|
|
|
@@ -23,6 +26,13 @@ from third_party.ibis.ibis_oracle.compiler import OracleCompiler
|
|
|
23
26
|
from third_party.ibis.ibis_oracle.datatypes import _get_type
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
EXTRA_RESERVED_WORDS = set(
|
|
30
|
+
[
|
|
31
|
+
"COLUMN",
|
|
32
|
+
]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
26
36
|
def _ora_denormalize_name(self, name):
|
|
27
37
|
"""Oracle specific version of sqlalchemy/engine/default.py.denormalize_name()
|
|
28
38
|
|
|
@@ -47,6 +57,11 @@ OracleDialect_cx_oracle.denormalize_name = _ora_denormalize_name
|
|
|
47
57
|
|
|
48
58
|
|
|
49
59
|
class DVTOracleIdentifierPreparer(OracleIdentifierPreparer):
|
|
60
|
+
|
|
61
|
+
reserved_words = {
|
|
62
|
+
x.lower() for x in ORACLE_RESERVED_WORDS.union(EXTRA_RESERVED_WORDS)
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
def quote_identifier(self, value):
|
|
51
66
|
"""Quote an identifier.
|
|
52
67
|
|
|
@@ -22,6 +22,7 @@ import snowflake.sqlalchemy
|
|
|
22
22
|
|
|
23
23
|
logging.getLogger("snowflake.connector").setLevel(logging.WARNING)
|
|
24
24
|
|
|
25
|
+
from data_validation.util import dvt_config_string_to_dict
|
|
25
26
|
import third_party.ibis.ibis_snowflake.datatypes
|
|
26
27
|
|
|
27
28
|
|
|
@@ -30,8 +31,10 @@ def snowflake_connect(
|
|
|
30
31
|
password: str,
|
|
31
32
|
account: str,
|
|
32
33
|
database: str,
|
|
33
|
-
connect_args:
|
|
34
|
+
connect_args: str = None,
|
|
34
35
|
):
|
|
36
|
+
if connect_args:
|
|
37
|
+
connect_args = dvt_config_string_to_dict(connect_args)
|
|
35
38
|
return ibis.snowflake.connect(
|
|
36
39
|
user=user,
|
|
37
40
|
password=password,
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
import pandas
|
|
15
15
|
import warnings
|
|
16
16
|
|
|
17
|
-
import json
|
|
18
17
|
import teradatasql
|
|
19
18
|
import ibis.expr.datatypes as dt
|
|
20
19
|
import ibis.expr.types as ir
|
|
21
20
|
from typing import Mapping, Any
|
|
22
21
|
import ibis.expr.schema as sch
|
|
23
22
|
from ibis.backends.base.sql import BaseSQLBackend
|
|
23
|
+
|
|
24
24
|
from third_party.ibis.ibis_teradata.compiler import TeradataCompiler
|
|
25
25
|
from third_party.ibis.ibis_teradata.datatypes import (
|
|
26
26
|
TeradataTypeTranslator,
|
|
@@ -41,7 +41,7 @@ class Backend(BaseSQLBackend):
|
|
|
41
41
|
port: int = 1025,
|
|
42
42
|
logmech: str = "TD2",
|
|
43
43
|
use_no_lock_tables: str = "False",
|
|
44
|
-
json_params: str = None,
|
|
44
|
+
json_params: Mapping[str, Any] = None,
|
|
45
45
|
) -> None:
|
|
46
46
|
self.teradata_config = {
|
|
47
47
|
"host": host,
|
|
@@ -50,15 +50,8 @@ class Backend(BaseSQLBackend):
|
|
|
50
50
|
"dbs_port": port,
|
|
51
51
|
"logmech": logmech,
|
|
52
52
|
}
|
|
53
|
-
|
|
54
53
|
if json_params:
|
|
55
|
-
|
|
56
|
-
param_dict = json.loads(json_params.replace("'", '"'))
|
|
57
|
-
self.teradata_config.update(param_dict)
|
|
58
|
-
except json.JSONDecodeError:
|
|
59
|
-
print(
|
|
60
|
-
f"Invalid JSON format in the parameter dictionary string: {json_params}"
|
|
61
|
-
)
|
|
54
|
+
self.teradata_config.update(json_params)
|
|
62
55
|
|
|
63
56
|
self.client = teradatasql.connect(**self.teradata_config)
|
|
64
57
|
self.con = self.client.cursor()
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
from third_party.ibis.ibis_teradata import Backend as TeradataBackend
|
|
15
15
|
import teradatasql # NOQA fail early if the package is missing
|
|
16
16
|
|
|
17
|
+
from data_validation.util import dvt_config_string_to_dict
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
def teradata_connect(
|
|
19
21
|
host: str = "localhost",
|
|
@@ -25,6 +27,9 @@ def teradata_connect(
|
|
|
25
27
|
json_params: str = None,
|
|
26
28
|
):
|
|
27
29
|
backend = TeradataBackend()
|
|
30
|
+
if json_params:
|
|
31
|
+
json_params = dvt_config_string_to_dict(json_params)
|
|
32
|
+
|
|
28
33
|
backend.do_connect(
|
|
29
34
|
host=host,
|
|
30
35
|
user_name=user_name,
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/cli_tools.py
RENAMED
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/client_info.py
RENAMED
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/clients.py
RENAMED
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/exceptions.py
RENAMED
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/find_tables.py
RENAMED
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/gcs_helper.py
RENAMED
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/raw_query.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/data_validation/state_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-7.2.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|