google-pso-data-validator 7.1.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.1.0 → google_pso_data_validator-7.3.0}/PKG-INFO +9 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/__main__.py +4 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/cli_tools.py +61 -20
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/combiner.py +141 -50
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/config_manager.py +23 -6
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/consts.py +25 -14
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/data_validation.py +6 -3
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/gcs_helper.py +22 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/result_handlers/bigquery.py +1 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/secret_manager.py +6 -2
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/state_manager.py +25 -1
- google_pso_data_validator-7.3.0/data_validation/util.py +64 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/PKG-INFO +9 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/SOURCES.txt +1 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/requires.txt +9 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/setup.py +10 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_addon/operations.py +66 -108
- google_pso_data_validator-7.3.0/third_party/ibis/ibis_mssql/registry.py +98 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/__init__.py +16 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/registry.py +2 -1
- google_pso_data_validator-7.3.0/third_party/ibis/ibis_postgres/registry.py +28 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_snowflake/api.py +4 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/__init__.py +3 -10
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/api.py +5 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/registry.py +3 -3
- google_pso_data_validator-7.1.0/data_validation/util.py +0 -24
- google_pso_data_validator-7.1.0/third_party/ibis/ibis_mssql/registry.py +0 -49
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/LICENSE +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/README.md +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/app.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/client_info.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/clients.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/exceptions.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/find_tables.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/jellyfish_distance.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/metadata.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/partition_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/partition_row_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/query_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/query_builder/random_row_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/raw_query.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/result_handlers/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/result_handlers/text.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/schema_validation.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/validation_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/setup.cfg +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_addon/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_biquery/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_biquery/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_db2/registry.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_impala/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
- {google_pso_data_validator-7.1.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.1.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.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/client.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_redshift/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: google-pso-data-validator
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.3.0
|
|
4
4
|
Summary: A package to enable easy data validation
|
|
5
5
|
Author: PSO DVT Engineering team
|
|
6
6
|
Author-email: data-validator-eng@google.com
|
|
@@ -38,6 +38,14 @@ Provides-Extra: apache-airflow
|
|
|
38
38
|
Requires-Dist: 1.10.11; extra == "apache-airflow"
|
|
39
39
|
Provides-Extra: pyspark
|
|
40
40
|
Requires-Dist: 3.0.0; extra == "pyspark"
|
|
41
|
+
Provides-Extra: develop
|
|
42
|
+
Requires-Dist: black==22.3.0; extra == "develop"
|
|
43
|
+
Requires-Dist: flake8; extra == "develop"
|
|
44
|
+
Requires-Dist: freezegun; extra == "develop"
|
|
45
|
+
Requires-Dist: pyfakefs==4.6.2; extra == "develop"
|
|
46
|
+
Requires-Dist: pytest; extra == "develop"
|
|
47
|
+
Requires-Dist: pytest-cov; extra == "develop"
|
|
48
|
+
Requires-Dist: pytest-timeout; extra == "develop"
|
|
41
49
|
Dynamic: author
|
|
42
50
|
Dynamic: author-email
|
|
43
51
|
Dynamic: classifier
|
{google_pso_data_validator-7.1.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
|
|
|
@@ -640,6 +639,10 @@ def run_connections(args):
|
|
|
640
639
|
"""Run commands related to connection management."""
|
|
641
640
|
if args.connect_cmd == "list":
|
|
642
641
|
cli_tools.list_connections()
|
|
642
|
+
elif args.connect_cmd == "delete":
|
|
643
|
+
cli_tools.delete_connection(args.connection_name)
|
|
644
|
+
elif args.connect_cmd == "describe":
|
|
645
|
+
cli_tools.describe_connection(args.connection_name, args.output_format)
|
|
643
646
|
elif args.connect_cmd == "add":
|
|
644
647
|
conn = cli_tools.get_connection_config_from_args(args)
|
|
645
648
|
# Test getting a client to validate connection details
|
{google_pso_data_validator-7.1.0 → google_pso_data_validator-7.3.0}/data_validation/cli_tools.py
RENAMED
|
@@ -52,12 +52,18 @@ import sys
|
|
|
52
52
|
import uuid
|
|
53
53
|
import os
|
|
54
54
|
import math
|
|
55
|
-
import re
|
|
56
55
|
from argparse import Namespace
|
|
57
56
|
from typing import Dict, List, Optional
|
|
58
57
|
from yaml import Dumper, Loader, dump, load
|
|
59
58
|
|
|
60
|
-
from data_validation import
|
|
59
|
+
from data_validation import (
|
|
60
|
+
clients,
|
|
61
|
+
consts,
|
|
62
|
+
find_tables,
|
|
63
|
+
state_manager,
|
|
64
|
+
gcs_helper,
|
|
65
|
+
util,
|
|
66
|
+
)
|
|
61
67
|
from data_validation.validation_builder import list_to_sublists
|
|
62
68
|
|
|
63
69
|
|
|
@@ -468,6 +474,27 @@ def _configure_connection_parser(subparsers):
|
|
|
468
474
|
)
|
|
469
475
|
_configure_database_specific_parsers(add_parser)
|
|
470
476
|
|
|
477
|
+
delete_parser = connect_subparsers.add_parser(
|
|
478
|
+
"delete", help="Delete an existing connection"
|
|
479
|
+
)
|
|
480
|
+
delete_parser.add_argument(
|
|
481
|
+
"--connection-name", "-c", required=True, help="Name of connection to delete"
|
|
482
|
+
)
|
|
483
|
+
describe_parser = connect_subparsers.add_parser(
|
|
484
|
+
"describe", help="Describe an existing connection"
|
|
485
|
+
)
|
|
486
|
+
describe_parser.add_argument(
|
|
487
|
+
"--connection-name", "-c", required=True, help="Name of connection to describe"
|
|
488
|
+
)
|
|
489
|
+
describe_parser.add_argument(
|
|
490
|
+
"--format",
|
|
491
|
+
"-f",
|
|
492
|
+
dest="output_format",
|
|
493
|
+
choices=["json", "yaml"],
|
|
494
|
+
default="yaml",
|
|
495
|
+
help="Output format for the configuration (default: yaml)",
|
|
496
|
+
)
|
|
497
|
+
|
|
471
498
|
|
|
472
499
|
def _configure_database_specific_parsers(parser):
|
|
473
500
|
"""Configure a separate subparser for each supported DB."""
|
|
@@ -1085,13 +1112,29 @@ def store_connection(connection_name, conn):
|
|
|
1085
1112
|
mgr.create_connection(connection_name, conn)
|
|
1086
1113
|
|
|
1087
1114
|
|
|
1115
|
+
def delete_connection(connection_name):
|
|
1116
|
+
"""Delete the connection config under the given name."""
|
|
1117
|
+
mgr = state_manager.StateManager()
|
|
1118
|
+
mgr.delete_connection(connection_name)
|
|
1119
|
+
|
|
1120
|
+
|
|
1088
1121
|
def list_connections():
|
|
1089
1122
|
"""List all saved connections."""
|
|
1090
1123
|
mgr = state_manager.StateManager()
|
|
1091
1124
|
connections = mgr.list_connections()
|
|
1092
1125
|
for conn_name in connections:
|
|
1093
1126
|
source_type = mgr.get_connection_config(conn_name).get("source_type")
|
|
1094
|
-
|
|
1127
|
+
print(f"Connection Name: {conn_name}")
|
|
1128
|
+
print(f"Source Type: {source_type}\n")
|
|
1129
|
+
return connections
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
def describe_connection(connection_name, output_format):
|
|
1133
|
+
"""Return yaml connection details for a specific connection"""
|
|
1134
|
+
mgr = state_manager.StateManager()
|
|
1135
|
+
connection_details = mgr.describe_connection(connection_name, output_format)
|
|
1136
|
+
print(connection_details)
|
|
1137
|
+
return connection_details
|
|
1095
1138
|
|
|
1096
1139
|
|
|
1097
1140
|
def get_connection(connection_name):
|
|
@@ -1165,31 +1208,29 @@ def get_filters(filter_value: str) -> List[Dict]:
|
|
|
1165
1208
|
If only one filter is specified, it applies to both source and target
|
|
1166
1209
|
For a doc on regular expression for filters see docs/internal/filters_regex.md
|
|
1167
1210
|
"""
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
)
|
|
1173
|
-
|
|
1174
|
-
if result := re.fullmatch(single_filter, filter_value):
|
|
1175
|
-
if result.group(0) == "":
|
|
1211
|
+
filters = util.split_not_in_quotes(filter_value, ":")
|
|
1212
|
+
if len(filters) not in (1, 2):
|
|
1213
|
+
raise argparse.ArgumentTypeError("Unable to parse filter arguments.")
|
|
1214
|
+
filters = [_.strip() for _ in filters]
|
|
1215
|
+
if len(filters) == 1:
|
|
1216
|
+
if not filters[0]:
|
|
1176
1217
|
raise argparse.ArgumentTypeError("Empty string not allowed in filter")
|
|
1177
1218
|
filter_dict = {
|
|
1178
1219
|
"type": "custom",
|
|
1179
|
-
"source":
|
|
1180
|
-
"target":
|
|
1220
|
+
"source": filters[0],
|
|
1221
|
+
"target": filters[0],
|
|
1181
1222
|
}
|
|
1182
|
-
elif
|
|
1183
|
-
if
|
|
1223
|
+
elif len(filters) == 2:
|
|
1224
|
+
if not filters[0] or not filters[1]:
|
|
1184
1225
|
raise argparse.ArgumentTypeError("Empty string not allowed in filter")
|
|
1185
1226
|
filter_dict = {
|
|
1186
1227
|
"type": "custom",
|
|
1187
|
-
"source":
|
|
1188
|
-
"target":
|
|
1228
|
+
"source": filters[0],
|
|
1229
|
+
"target": filters[1],
|
|
1189
1230
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1231
|
+
filter_config = [
|
|
1232
|
+
filter_dict,
|
|
1233
|
+
]
|
|
1193
1234
|
return filter_config
|
|
1194
1235
|
|
|
1195
1236
|
|
{google_pso_data_validator-7.1.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,20 +118,26 @@ 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():
|
|
127
|
-
source_value =
|
|
128
|
-
|
|
135
|
+
source_value = (
|
|
136
|
+
field_differences["differences_source_value"].epoch_seconds().cast("int64")
|
|
137
|
+
)
|
|
138
|
+
target_value = (
|
|
139
|
+
field_differences["differences_target_value"].epoch_seconds().cast("int64")
|
|
140
|
+
)
|
|
129
141
|
elif datatype.is_boolean() or (target_type and target_type.is_boolean()):
|
|
130
142
|
source_value = field_differences["differences_source_value"].cast("boolean")
|
|
131
143
|
target_value = field_differences["differences_target_value"].cast("boolean")
|
|
@@ -206,12 +218,16 @@ def _calculate_difference(
|
|
|
206
218
|
difference.name("difference"),
|
|
207
219
|
pct_difference.name("pct_difference"),
|
|
208
220
|
pct_threshold.name("pct_threshold"),
|
|
209
|
-
validation_status.name(
|
|
221
|
+
validation_status.name(consts.VALIDATION_STATUS),
|
|
210
222
|
)
|
|
211
223
|
|
|
212
224
|
|
|
213
225
|
def _calculate_differences(
|
|
214
|
-
source
|
|
226
|
+
source: "IbisTable",
|
|
227
|
+
target: "IbisTable",
|
|
228
|
+
join_on_fields: tuple,
|
|
229
|
+
validations: "dict[ValidationMetadata]",
|
|
230
|
+
is_value_comparison: bool,
|
|
215
231
|
):
|
|
216
232
|
"""Calculate differences between source and target fields.
|
|
217
233
|
|
|
@@ -263,7 +279,12 @@ def _calculate_differences(
|
|
|
263
279
|
return differences_pivot
|
|
264
280
|
|
|
265
281
|
|
|
266
|
-
def _pivot_result(
|
|
282
|
+
def _pivot_result(
|
|
283
|
+
result: "IbisTable",
|
|
284
|
+
join_on_fields: tuple,
|
|
285
|
+
validations: "dict[ValidationMetadata]",
|
|
286
|
+
result_type: str,
|
|
287
|
+
):
|
|
267
288
|
all_fields = frozenset(result.schema().names)
|
|
268
289
|
validation_fields = (
|
|
269
290
|
all_fields - frozenset(join_on_fields)
|
|
@@ -281,19 +302,21 @@ def _pivot_result(result, join_on_fields, validations, result_type):
|
|
|
281
302
|
ibis.literal("{")
|
|
282
303
|
+ ibis.literal(", ").join(validation.primary_keys)
|
|
283
304
|
+ ibis.literal("}")
|
|
284
|
-
).name(
|
|
305
|
+
).name(consts.CONFIG_PRIMARY_KEYS)
|
|
285
306
|
else:
|
|
286
|
-
primary_keys =
|
|
307
|
+
primary_keys = (
|
|
308
|
+
ibis.literal(None).cast("string").name(consts.CONFIG_PRIMARY_KEYS)
|
|
309
|
+
)
|
|
287
310
|
|
|
288
311
|
pivots.append(
|
|
289
312
|
result.projection(
|
|
290
313
|
(
|
|
291
314
|
ibis.literal(field).name("validation_name"),
|
|
292
315
|
ibis.literal(validation.validation_type).name(
|
|
293
|
-
|
|
316
|
+
consts.VALIDATION_TYPE
|
|
294
317
|
),
|
|
295
318
|
ibis.literal(validation.aggregation_type).name(
|
|
296
|
-
|
|
319
|
+
consts.AGGREGATION_TYPE
|
|
297
320
|
),
|
|
298
321
|
ibis.literal(validation.get_table_name(result_type)).name(
|
|
299
322
|
"table_name"
|
|
@@ -329,7 +352,12 @@ def _as_json(expr):
|
|
|
329
352
|
)
|
|
330
353
|
|
|
331
354
|
|
|
332
|
-
def _join_pivots(
|
|
355
|
+
def _join_pivots(
|
|
356
|
+
source: "IbisTable",
|
|
357
|
+
target: "IbisTable",
|
|
358
|
+
differences: "IbisTable",
|
|
359
|
+
join_on_fields: tuple,
|
|
360
|
+
):
|
|
333
361
|
if join_on_fields:
|
|
334
362
|
join_values = []
|
|
335
363
|
for field in join_on_fields:
|
|
@@ -342,62 +370,125 @@ def _join_pivots(source, target, differences, join_on_fields):
|
|
|
342
370
|
|
|
343
371
|
group_by_columns = (
|
|
344
372
|
ibis.literal("{") + ibis.literal(", ").join(join_values) + ibis.literal("}")
|
|
345
|
-
).name(
|
|
373
|
+
).name(consts.GROUP_BY_COLUMNS)
|
|
346
374
|
else:
|
|
347
|
-
group_by_columns =
|
|
375
|
+
group_by_columns = (
|
|
376
|
+
ibis.literal(None).cast("string").name(consts.GROUP_BY_COLUMNS)
|
|
377
|
+
)
|
|
348
378
|
|
|
349
379
|
join_keys = ("validation_name",) + join_on_fields
|
|
350
380
|
source_difference = source.join(differences, join_keys, how="outer")[
|
|
351
381
|
[source[field] for field in join_keys]
|
|
352
382
|
+ [
|
|
353
|
-
source[
|
|
354
|
-
source[
|
|
355
|
-
source[
|
|
383
|
+
source[consts.VALIDATION_TYPE],
|
|
384
|
+
source[consts.AGGREGATION_TYPE],
|
|
385
|
+
source[consts.CONFIG_TABLE_NAME],
|
|
356
386
|
source["column_name"],
|
|
357
|
-
source[
|
|
387
|
+
source[consts.CONFIG_PRIMARY_KEYS],
|
|
358
388
|
source["num_random_rows"],
|
|
359
389
|
source["agg_value"],
|
|
360
390
|
differences["difference"],
|
|
361
391
|
differences["pct_difference"],
|
|
362
392
|
differences["pct_threshold"],
|
|
363
|
-
differences[
|
|
393
|
+
differences[consts.VALIDATION_STATUS],
|
|
364
394
|
]
|
|
365
395
|
]
|
|
366
396
|
joined = source_difference.join(target, join_keys, how="outer")[
|
|
367
397
|
source_difference["validation_name"],
|
|
368
|
-
source_difference[
|
|
369
|
-
.fillna(target[
|
|
370
|
-
.name(
|
|
371
|
-
source_difference[
|
|
372
|
-
.fillna(target[
|
|
373
|
-
.name(
|
|
374
|
-
source_difference["table_name"].name(
|
|
375
|
-
source_difference["column_name"].name(
|
|
376
|
-
source_difference["agg_value"].name(
|
|
377
|
-
target["table_name"].name(
|
|
378
|
-
target["column_name"].name(
|
|
379
|
-
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),
|
|
380
410
|
group_by_columns,
|
|
381
|
-
source_difference[
|
|
411
|
+
source_difference[consts.CONFIG_PRIMARY_KEYS],
|
|
382
412
|
source_difference["num_random_rows"],
|
|
383
413
|
source_difference["difference"],
|
|
384
414
|
source_difference["pct_difference"],
|
|
385
415
|
source_difference["pct_threshold"],
|
|
386
|
-
source_difference[
|
|
416
|
+
source_difference[consts.VALIDATION_STATUS],
|
|
387
417
|
]
|
|
388
418
|
return joined
|
|
389
419
|
|
|
390
420
|
|
|
391
|
-
def _add_metadata(joined, run_metadata):
|
|
421
|
+
def _add_metadata(joined: "IbisTable", run_metadata: "RunMetadata"):
|
|
392
422
|
# TODO: Add source and target queries to metadata
|
|
393
423
|
run_metadata.end_time = datetime.datetime.now(datetime.timezone.utc)
|
|
394
424
|
|
|
395
425
|
joined = joined[
|
|
396
426
|
joined,
|
|
397
|
-
ibis.literal(run_metadata.run_id).name(
|
|
398
|
-
ibis.literal(run_metadata.labels).name(
|
|
399
|
-
ibis.literal(run_metadata.start_time).name(
|
|
400
|
-
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),
|
|
401
431
|
]
|
|
402
432
|
|
|
403
|
-
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
|
+
)
|
|
@@ -784,7 +784,9 @@ class ConfigManager(object):
|
|
|
784
784
|
column_position: int,
|
|
785
785
|
) -> dict:
|
|
786
786
|
"""Append calculated field for length() or epoch_seconds(timestamp) for preprocessing before column validation aggregation."""
|
|
787
|
-
depth
|
|
787
|
+
depth = 0
|
|
788
|
+
cast_type = None
|
|
789
|
+
final_cast_type = None
|
|
788
790
|
if any(_ in ["json", "!json"] for _ in [column_type, target_column_type]):
|
|
789
791
|
# JSON data which needs casting to string before we apply a length function.
|
|
790
792
|
pre_calculated_config = self.build_and_append_pre_agg_calc_config(
|
|
@@ -829,6 +831,10 @@ class ConfigManager(object):
|
|
|
829
831
|
depth = 1
|
|
830
832
|
|
|
831
833
|
calc_func = "epoch_seconds"
|
|
834
|
+
if agg_type == consts.CONFIG_TYPE_SUM:
|
|
835
|
+
# It is possible to exceed int64 when summing epoch_seconds therefore cast to string.
|
|
836
|
+
# See issue 1391 for details.
|
|
837
|
+
final_cast_type = "string"
|
|
832
838
|
|
|
833
839
|
elif column_type == "int32" or column_type == "!int32":
|
|
834
840
|
calc_func = consts.CONFIG_CAST
|
|
@@ -856,6 +862,9 @@ class ConfigManager(object):
|
|
|
856
862
|
),
|
|
857
863
|
consts.CONFIG_TYPE: agg_type,
|
|
858
864
|
}
|
|
865
|
+
if final_cast_type:
|
|
866
|
+
# Adding to dict this way to avoid adding a lot of empty cast attributes.
|
|
867
|
+
aggregate_config[consts.CONFIG_CAST] = final_cast_type
|
|
859
868
|
|
|
860
869
|
return aggregate_config
|
|
861
870
|
|
|
@@ -863,22 +872,28 @@ class ConfigManager(object):
|
|
|
863
872
|
self,
|
|
864
873
|
source_column_ibis_type: dt.DataType,
|
|
865
874
|
target_column_ibis_type: dt.DataType,
|
|
875
|
+
margin: int = 0,
|
|
866
876
|
) -> bool:
|
|
867
|
-
"""Identifies Decimal columns that will cause problems in a Pandas Dataframe
|
|
868
|
-
|
|
877
|
+
"""Identifies Decimal columns that will cause problems in a Pandas Dataframe.
|
|
878
|
+
|
|
879
|
+
i.e. are of greater precision than a 64bit int/real can hold.
|
|
880
|
+
|
|
881
|
+
margin: Allows us to lower the precision threshold. This is helpful when summing column
|
|
882
|
+
values that are okay by themselves but cumulativaly could overflow a 64bit value.
|
|
883
|
+
"""
|
|
869
884
|
return bool(
|
|
870
885
|
(
|
|
871
886
|
isinstance(source_column_ibis_type, dt.Decimal)
|
|
872
887
|
and (
|
|
873
888
|
source_column_ibis_type.precision is None
|
|
874
|
-
or source_column_ibis_type.precision > 18
|
|
889
|
+
or source_column_ibis_type.precision > (18 - margin)
|
|
875
890
|
)
|
|
876
891
|
)
|
|
877
892
|
and (
|
|
878
893
|
isinstance(target_column_ibis_type, dt.Decimal)
|
|
879
894
|
and (
|
|
880
895
|
target_column_ibis_type.precision is None
|
|
881
|
-
or target_column_ibis_type.precision > 18
|
|
896
|
+
or target_column_ibis_type.precision > (18 - margin)
|
|
882
897
|
)
|
|
883
898
|
)
|
|
884
899
|
)
|
|
@@ -1042,7 +1057,9 @@ class ConfigManager(object):
|
|
|
1042
1057
|
consts.CONFIG_TYPE: agg_type,
|
|
1043
1058
|
}
|
|
1044
1059
|
if self._decimal_column_too_big_for_pandas(
|
|
1045
|
-
source_column_ibis_type,
|
|
1060
|
+
source_column_ibis_type,
|
|
1061
|
+
target_column_ibis_type,
|
|
1062
|
+
margin=(2 if agg_type == consts.CONFIG_TYPE_SUM else 0),
|
|
1046
1063
|
):
|
|
1047
1064
|
aggregate_config[consts.CONFIG_CAST] = "string"
|
|
1048
1065
|
|
{google_pso_data_validator-7.1.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,
|