google-pso-data-validator 7.1.0__tar.gz → 7.2.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.2.0}/PKG-INFO +9 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/__main__.py +4 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/cli_tools.py +61 -20
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/combiner.py +6 -2
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/config_manager.py +23 -6
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/gcs_helper.py +22 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/state_manager.py +25 -1
- google_pso_data_validator-7.2.0/data_validation/util.py +46 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/google_pso_data_validator.egg-info/PKG-INFO +9 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/google_pso_data_validator.egg-info/SOURCES.txt +1 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/google_pso_data_validator.egg-info/requires.txt +9 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/setup.py +10 -1
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_addon/operations.py +66 -108
- google_pso_data_validator-7.2.0/third_party/ibis/ibis_mssql/registry.py +98 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_oracle/registry.py +2 -1
- google_pso_data_validator-7.2.0/third_party/ibis/ibis_postgres/registry.py +28 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.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.2.0}/LICENSE +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/README.md +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/app.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/client_info.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/clients.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/consts.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/data_validation.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/exceptions.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/find_tables.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/jellyfish_distance.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/metadata.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/partition_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/query_builder/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/query_builder/partition_row_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/query_builder/query_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/query_builder/random_row_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/raw_query.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/result_handlers/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/result_handlers/bigquery.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/result_handlers/text.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/schema_validation.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/secret_manager.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/validation_builder.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/setup.cfg +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_addon/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_biquery/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_biquery/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_db2/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_db2/registry.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_impala/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_mssql/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.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.2.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.2.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_oracle/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_postgres/client.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_redshift/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_teradata/api.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
- {google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.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.2.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.2.0}/data_validation/__main__.py
RENAMED
|
@@ -640,6 +640,10 @@ def run_connections(args):
|
|
|
640
640
|
"""Run commands related to connection management."""
|
|
641
641
|
if args.connect_cmd == "list":
|
|
642
642
|
cli_tools.list_connections()
|
|
643
|
+
elif args.connect_cmd == "delete":
|
|
644
|
+
cli_tools.delete_connection(args.connection_name)
|
|
645
|
+
elif args.connect_cmd == "describe":
|
|
646
|
+
cli_tools.describe_connection(args.connection_name, args.output_format)
|
|
643
647
|
elif args.connect_cmd == "add":
|
|
644
648
|
conn = cli_tools.get_connection_config_from_args(args)
|
|
645
649
|
# Test getting a client to validate connection details
|
{google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.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.2.0}/data_validation/combiner.py
RENAMED
|
@@ -124,8 +124,12 @@ def _calculate_difference(
|
|
|
124
124
|
):
|
|
125
125
|
pct_threshold = ibis.literal(validation.threshold)
|
|
126
126
|
if datatype.is_timestamp() or datatype.is_date():
|
|
127
|
-
source_value =
|
|
128
|
-
|
|
127
|
+
source_value = (
|
|
128
|
+
field_differences["differences_source_value"].epoch_seconds().cast("int64")
|
|
129
|
+
)
|
|
130
|
+
target_value = (
|
|
131
|
+
field_differences["differences_target_value"].epoch_seconds().cast("int64")
|
|
132
|
+
)
|
|
129
133
|
elif datatype.is_boolean() or (target_type and target_type.is_boolean()):
|
|
130
134
|
source_value = field_differences["differences_source_value"].cast("boolean")
|
|
131
135
|
target_value = field_differences["differences_target_value"].cast("boolean")
|
|
@@ -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.2.0}/data_validation/gcs_helper.py
RENAMED
|
@@ -21,6 +21,7 @@ from data_validation import client_info
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
WRITE_SUCCESS_STRING = "Success! Config output written to"
|
|
24
|
+
DELETE_SUCCESS_STRING = "Successfully deleted"
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def _is_gcs_path(file_path: str) -> bool:
|
|
@@ -67,6 +68,13 @@ def _write_gcs_file(file_path: str, data: str):
|
|
|
67
68
|
blob.upload_from_string(data)
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
def _delete_gcs_file(file_path: str):
|
|
72
|
+
"""Delete a file stored in GCS."""
|
|
73
|
+
gcs_bucket = get_gcs_bucket(file_path)
|
|
74
|
+
blob = gcs_bucket.blob(_get_gcs_file_path(file_path))
|
|
75
|
+
blob.delete()
|
|
76
|
+
|
|
77
|
+
|
|
70
78
|
def read_file(file_path: str, download_as_text: bool = False):
|
|
71
79
|
if _is_gcs_path(file_path):
|
|
72
80
|
return _read_gcs_file(file_path, download_as_text)
|
|
@@ -87,6 +95,20 @@ def write_file(file_path: str, data: str, include_log: bool = True):
|
|
|
87
95
|
logging.info(f"{WRITE_SUCCESS_STRING} {file_path}")
|
|
88
96
|
|
|
89
97
|
|
|
98
|
+
def delete_file(file_path: str, include_log: bool = True):
|
|
99
|
+
"""Delete a file from GCS or local filesystem, depending on the path."""
|
|
100
|
+
if _is_gcs_path(file_path):
|
|
101
|
+
_delete_gcs_file(file_path)
|
|
102
|
+
else:
|
|
103
|
+
if os.path.exists(file_path):
|
|
104
|
+
os.remove(file_path)
|
|
105
|
+
else:
|
|
106
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
107
|
+
|
|
108
|
+
if include_log:
|
|
109
|
+
logging.info(f"{DELETE_SUCCESS_STRING}: {file_path}")
|
|
110
|
+
|
|
111
|
+
|
|
90
112
|
def list_gcs_directory(directory_path: str) -> List[str]:
|
|
91
113
|
gcs_prefix = _get_gcs_file_path(directory_path)
|
|
92
114
|
gcs_bucket = get_gcs_bucket(directory_path)
|
{google_pso_data_validator-7.1.0 → google_pso_data_validator-7.2.0}/data_validation/state_manager.py
RENAMED
|
@@ -19,8 +19,9 @@ and validation files.
|
|
|
19
19
|
|
|
20
20
|
import enum
|
|
21
21
|
import json
|
|
22
|
+
import yaml
|
|
22
23
|
import os
|
|
23
|
-
from typing import Dict, List
|
|
24
|
+
from typing import Dict, List, Union
|
|
24
25
|
|
|
25
26
|
from data_validation import consts, gcs_helper
|
|
26
27
|
|
|
@@ -59,6 +60,15 @@ class StateManager(object):
|
|
|
59
60
|
connection_path = self._get_connection_path(name)
|
|
60
61
|
gcs_helper.write_file(connection_path, json.dumps(config))
|
|
61
62
|
|
|
63
|
+
def delete_connection(self, name: str):
|
|
64
|
+
"""Delete an existing connection file by its connection name.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
name (String): The name of the connection.
|
|
68
|
+
"""
|
|
69
|
+
connection_path = self._get_connection_path(name)
|
|
70
|
+
gcs_helper.delete_file(connection_path)
|
|
71
|
+
|
|
62
72
|
def get_connection_config(self, name: str) -> Dict[str, str]:
|
|
63
73
|
"""Get a connection configuration from the expected file.
|
|
64
74
|
|
|
@@ -72,6 +82,20 @@ class StateManager(object):
|
|
|
72
82
|
|
|
73
83
|
return json.loads(conn_str)
|
|
74
84
|
|
|
85
|
+
def describe_connection(
|
|
86
|
+
self, connection_name: str, output_format: str
|
|
87
|
+
) -> Union[dict, str]:
|
|
88
|
+
"""Describe an existing connection by returning connection details in yaml format.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
args (): The name of the connection.
|
|
92
|
+
"""
|
|
93
|
+
conn_data = self.get_connection_config(connection_name)
|
|
94
|
+
if output_format == "json":
|
|
95
|
+
return conn_data
|
|
96
|
+
elif output_format == "yaml":
|
|
97
|
+
return yaml.dump(conn_data, default_flow_style=False, sort_keys=False)
|
|
98
|
+
|
|
75
99
|
def list_connections(self) -> List[str]:
|
|
76
100
|
"""Returns a list of the connection names that exist."""
|
|
77
101
|
file_names = self._list_directory(self._get_connections_directory())
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import re
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def timed_call(log_txt, fn, *args, **kwargs):
|
|
21
|
+
t0 = time.time()
|
|
22
|
+
result = fn(*args, **kwargs)
|
|
23
|
+
elapsed = time.time() - t0
|
|
24
|
+
logging.debug(f"{log_txt} elapsed: {round(elapsed, 2)}s")
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def split_not_in_quotes(
|
|
29
|
+
to_split: str, sep: str = " ", exclude_empty_tokens: bool = False
|
|
30
|
+
) -> list:
|
|
31
|
+
"""Split a string by a separator but only when the separator is not inside quotes.
|
|
32
|
+
re pattern taken from this comment:
|
|
33
|
+
https://stackoverflow.com/a/2787979/10979853
|
|
34
|
+
The commenter's words should the link ever go stale:
|
|
35
|
+
Each time it finds a semicolon, the lookahead scans the entire remaining string,
|
|
36
|
+
making sure there's an even number of single-quotes and an even number of double-quotes.
|
|
37
|
+
(Single-quotes inside double-quoted fields, or vice-versa, are ignored.) If the
|
|
38
|
+
lookahead succeeds, the semicolon is a delimiter.
|
|
39
|
+
The pattern doesn't cope with whitespace as sep, back to back spaces are multiple seps, therefore
|
|
40
|
+
we have exclude_empty_tokens parameter.
|
|
41
|
+
"""
|
|
42
|
+
pattern = r"""%(sep)s(?=(?:[^'"]|'[^']*'|"[^"]*")*$)""" % {"sep": sep}
|
|
43
|
+
if exclude_empty_tokens:
|
|
44
|
+
return [t for t in re.split(pattern, to_split) if t]
|
|
45
|
+
else:
|
|
46
|
+
return re.split(pattern, to_split)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: google-pso-data-validator
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.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
|
|
@@ -74,6 +74,7 @@ third_party/ibis/ibis_oracle/registry.py
|
|
|
74
74
|
third_party/ibis/ibis_postgres/__init__.py
|
|
75
75
|
third_party/ibis/ibis_postgres/client.py
|
|
76
76
|
third_party/ibis/ibis_postgres/datatypes.py
|
|
77
|
+
third_party/ibis/ibis_postgres/registry.py
|
|
77
78
|
third_party/ibis/ibis_redshift/__init__.py
|
|
78
79
|
third_party/ibis/ibis_redshift/api.py
|
|
79
80
|
third_party/ibis/ibis_redshift/compiler.py
|
|
@@ -18,7 +18,7 @@ import setuptools
|
|
|
18
18
|
|
|
19
19
|
name = "google-pso-data-validator"
|
|
20
20
|
description = "A package to enable easy data validation"
|
|
21
|
-
version = "7.
|
|
21
|
+
version = "7.2.0"
|
|
22
22
|
release_status = "Development Status :: 4 - Beta"
|
|
23
23
|
|
|
24
24
|
with open("README.md", "r") as fh:
|
|
@@ -50,6 +50,15 @@ dependencies = [
|
|
|
50
50
|
extras_require = {
|
|
51
51
|
"apache-airflow": "1.10.11",
|
|
52
52
|
"pyspark": "3.0.0",
|
|
53
|
+
"develop": [
|
|
54
|
+
"black==22.3.0",
|
|
55
|
+
"flake8",
|
|
56
|
+
"freezegun",
|
|
57
|
+
"pyfakefs==4.6.2",
|
|
58
|
+
"pytest",
|
|
59
|
+
"pytest-cov",
|
|
60
|
+
"pytest-timeout",
|
|
61
|
+
],
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
packages = [
|