google-pso-data-validator 8.5.2__tar.gz → 8.6.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-8.5.2/google_pso_data_validator.egg-info → google_pso_data_validator-8.6.0}/PKG-INFO +12 -2
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/README.md +10 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/__init__.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/cli_tools.py +3 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/client_info.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/clients.py +4 -4
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/config_manager.py +16 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/find_tables.py +34 -10
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/gcs_helper.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/random_row_builder.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/base_backend.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/bigquery.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/postgres.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/schema_validation.py +4 -2
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/util.py +17 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0/google_pso_data_validator.egg-info}/PKG-INFO +12 -2
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/requires.txt +1 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/setup.py +2 -2
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_addon/api.py +44 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_addon/operations.py +19 -18
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -1
- google_pso_data_validator-8.6.0/third_party/ibis/ibis_db2/__init__.py +260 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/datatypes.py +17 -2
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/registry.py +11 -4
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_impala/api.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/__init__.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/__init__.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/datatypes.py +3 -3
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/registry.py +6 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_redshift/__init__.py +16 -9
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/__init__.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/datatypes.py +0 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/__init__.py +3 -3
- google_pso_data_validator-8.5.2/third_party/ibis/ibis_db2/__init__.py +0 -172
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/LICENSE +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/__main__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/app.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/combiner.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/consts.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/data_validation.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/exceptions.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/jellyfish_distance.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/metadata.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/partition_builder.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/partition_row_builder.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/query_builder.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/raw_query.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/factory.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/text.py +1 -1
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/secret_manager.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/state_manager.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/validation_builder.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/setup.cfg +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_bigquery/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_bigquery/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_bigquery/registry.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/client.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_redshift/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/compiler.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/registry.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/api.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
- {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: google-pso-data-validator
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.6.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
|
|
@@ -39,7 +39,7 @@ Requires-Dist: 1.10.11; extra == "apache-airflow"
|
|
|
39
39
|
Provides-Extra: pyspark
|
|
40
40
|
Requires-Dist: 3.0.0; extra == "pyspark"
|
|
41
41
|
Provides-Extra: develop
|
|
42
|
-
Requires-Dist: black==
|
|
42
|
+
Requires-Dist: black==26.1.0; extra == "develop"
|
|
43
43
|
Requires-Dist: flake8; extra == "develop"
|
|
44
44
|
Requires-Dist: freezegun; extra == "develop"
|
|
45
45
|
Requires-Dist: pyfakefs; extra == "develop"
|
|
@@ -719,17 +719,27 @@ target. The `find-tables` command:
|
|
|
719
719
|
- Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
|
|
720
720
|
- Pulls all tables from the target
|
|
721
721
|
- Uses Jaro Similarity algorithm to match tables
|
|
722
|
+
- Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
|
|
722
723
|
- Finally, it prints a JSON list of tables which can be a reference for the
|
|
723
724
|
validation run config.
|
|
724
725
|
|
|
725
726
|
Note that our default value for the `score-cutoff` parameter is 1 and it seeks for identical matches. If no matches occur, reduce this value as deemed necessary. By using smaller numbers such as 0.7, 0.65 etc you can get more matches. For reference, we make use of [this jaro_similarity method](https://jamesturk.github.io/jellyfish/functions/#jaro-similarity) for the string comparison.
|
|
726
727
|
|
|
728
|
+
|
|
727
729
|
```shell
|
|
728
730
|
data-validation find-tables --source-conn source --target-conn target \
|
|
729
731
|
--allowed-schemas pso_data_validator \
|
|
730
732
|
--score-cutoff 1
|
|
731
733
|
```
|
|
732
734
|
|
|
735
|
+
If your source and target environments have different schema names (e.g., `prod_raw` vs `dwh_raw`), you can provide a mapping directly in the `--allowed-schemas` flag using the `source_schema=target_schema` syntax.
|
|
736
|
+
|
|
737
|
+
```shell
|
|
738
|
+
data-validation find-tables --source-conn source --target-conn target \
|
|
739
|
+
--allowed-schemas prod=dwh \
|
|
740
|
+
--score-cutoff 1
|
|
741
|
+
```
|
|
742
|
+
|
|
733
743
|
### Using Beta CLI Features
|
|
734
744
|
|
|
735
745
|
There may be occasions we want to release a new CLI feature under a Beta flag.
|
|
@@ -659,17 +659,27 @@ target. The `find-tables` command:
|
|
|
659
659
|
- Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
|
|
660
660
|
- Pulls all tables from the target
|
|
661
661
|
- Uses Jaro Similarity algorithm to match tables
|
|
662
|
+
- Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
|
|
662
663
|
- Finally, it prints a JSON list of tables which can be a reference for the
|
|
663
664
|
validation run config.
|
|
664
665
|
|
|
665
666
|
Note that our default value for the `score-cutoff` parameter is 1 and it seeks for identical matches. If no matches occur, reduce this value as deemed necessary. By using smaller numbers such as 0.7, 0.65 etc you can get more matches. For reference, we make use of [this jaro_similarity method](https://jamesturk.github.io/jellyfish/functions/#jaro-similarity) for the string comparison.
|
|
666
667
|
|
|
668
|
+
|
|
667
669
|
```shell
|
|
668
670
|
data-validation find-tables --source-conn source --target-conn target \
|
|
669
671
|
--allowed-schemas pso_data_validator \
|
|
670
672
|
--score-cutoff 1
|
|
671
673
|
```
|
|
672
674
|
|
|
675
|
+
If your source and target environments have different schema names (e.g., `prod_raw` vs `dwh_raw`), you can provide a mapping directly in the `--allowed-schemas` flag using the `source_schema=target_schema` syntax.
|
|
676
|
+
|
|
677
|
+
```shell
|
|
678
|
+
data-validation find-tables --source-conn source --target-conn target \
|
|
679
|
+
--allowed-schemas prod=dwh \
|
|
680
|
+
--score-cutoff 1
|
|
681
|
+
```
|
|
682
|
+
|
|
673
683
|
### Using Beta CLI Features
|
|
674
684
|
|
|
675
685
|
There may be occasions we want to release a new CLI feature under a Beta flag.
|
{google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/cli_tools.py
RENAMED
|
@@ -404,7 +404,9 @@ def _configure_find_tables(subparsers):
|
|
|
404
404
|
"--target-conn", "-tc", help="Target connection name."
|
|
405
405
|
)
|
|
406
406
|
find_tables_parser.add_argument(
|
|
407
|
-
"--allowed-schemas",
|
|
407
|
+
"--allowed-schemas",
|
|
408
|
+
"-as",
|
|
409
|
+
help="List of source schemas to match. Can include mappings like 'source_schema=target_schema'.",
|
|
408
410
|
)
|
|
409
411
|
find_tables_parser.add_argument(
|
|
410
412
|
"--include-views",
|
{google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/clients.py
RENAMED
|
@@ -342,10 +342,10 @@ def get_data_client(connection_config):
|
|
|
342
342
|
consts.GOOGLE_SERVICE_ACCOUNT_KEY_PATH
|
|
343
343
|
)
|
|
344
344
|
if key_path:
|
|
345
|
-
decrypted_connection_config[
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
345
|
+
decrypted_connection_config["credentials"] = (
|
|
346
|
+
google.oauth2.service_account.Credentials.from_service_account_file(
|
|
347
|
+
key_path
|
|
348
|
+
)
|
|
349
349
|
)
|
|
350
350
|
|
|
351
351
|
if source_type not in CLIENT_LOOKUP:
|
|
@@ -667,6 +667,14 @@ class ConfigManager(object):
|
|
|
667
667
|
isinstance(source_type, dt.UUID) or isinstance(target_type, dt.UUID)
|
|
668
668
|
)
|
|
669
669
|
|
|
670
|
+
def _is_db2_blob(self, source_column_name: str, target_column_name: str) -> bool:
|
|
671
|
+
"""Returns True when either source or target column is Db2 (LUW and z/OS) BLOB data type."""
|
|
672
|
+
return self._is_raw_data_type(
|
|
673
|
+
"db2", source_column_name, target_column_name, ["BLOB"]
|
|
674
|
+
) or self._is_raw_data_type(
|
|
675
|
+
"db2_zos", source_column_name, target_column_name, ["BLOB"]
|
|
676
|
+
)
|
|
677
|
+
|
|
670
678
|
def _is_db2_xml(self, source_column_name: str, target_column_name: str) -> bool:
|
|
671
679
|
"""Returns True when either source or target column is Oracle LOB data type."""
|
|
672
680
|
return self._is_raw_data_type(
|
|
@@ -1351,6 +1359,14 @@ class ConfigManager(object):
|
|
|
1351
1359
|
)
|
|
1352
1360
|
result_source_columns.pop(source_column)
|
|
1353
1361
|
result_target_columns.pop(target_column)
|
|
1362
|
+
if self._is_db2_blob(source_column, target_column):
|
|
1363
|
+
# Db2 BLOB is incompatible with Db2s own HEX function.
|
|
1364
|
+
# Users would need to use a custom query to a custom UDF to row validate these.
|
|
1365
|
+
logging.info(
|
|
1366
|
+
f"Skipping column {source_column} due to Db2 BLOB data type"
|
|
1367
|
+
)
|
|
1368
|
+
result_source_columns.pop(source_column)
|
|
1369
|
+
result_target_columns.pop(target_column)
|
|
1354
1370
|
|
|
1355
1371
|
return result_source_columns, result_target_columns
|
|
1356
1372
|
|
{google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/find_tables.py
RENAMED
|
@@ -28,26 +28,37 @@ if TYPE_CHECKING:
|
|
|
28
28
|
import ibis
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def _compare_match_tables(
|
|
31
|
+
def _compare_match_tables(
|
|
32
|
+
source_table_map: dict,
|
|
33
|
+
target_table_map: dict,
|
|
34
|
+
score_cutoff: float = 0.8,
|
|
35
|
+
schema_map: dict = None,
|
|
36
|
+
) -> list:
|
|
32
37
|
"""Return dict config object from matching tables."""
|
|
33
38
|
# TODO(dhercher): evaluate if improved comparison and score cutoffs should be used.
|
|
34
39
|
table_configs = []
|
|
40
|
+
schema_map = schema_map or {}
|
|
35
41
|
|
|
36
42
|
target_keys = target_table_map.keys()
|
|
37
43
|
for source_key in source_table_map:
|
|
44
|
+
source_schema = source_table_map[source_key][consts.CONFIG_SCHEMA_NAME]
|
|
45
|
+
source_table = source_table_map[source_key][consts.CONFIG_TABLE_NAME]
|
|
46
|
+
|
|
47
|
+
# Apply schema mapping if it exists, when there is a mapping
|
|
48
|
+
# lookup_key is {mapped_schema}.{source_name} which should then
|
|
49
|
+
# find a match in target_keys.
|
|
50
|
+
lookup_schema = schema_map.get(source_schema, source_schema)
|
|
51
|
+
lookup_key = f"{lookup_schema}.{source_table}".casefold()
|
|
52
|
+
|
|
38
53
|
target_key = jellyfish_distance.extract_closest_match(
|
|
39
|
-
|
|
54
|
+
lookup_key, target_keys, score_cutoff=score_cutoff
|
|
40
55
|
)
|
|
41
56
|
if target_key is None:
|
|
42
57
|
continue
|
|
43
58
|
|
|
44
59
|
table_config = {
|
|
45
|
-
consts.CONFIG_SCHEMA_NAME:
|
|
46
|
-
|
|
47
|
-
],
|
|
48
|
-
consts.CONFIG_TABLE_NAME: source_table_map[source_key][
|
|
49
|
-
consts.CONFIG_TABLE_NAME
|
|
50
|
-
],
|
|
60
|
+
consts.CONFIG_SCHEMA_NAME: source_schema,
|
|
61
|
+
consts.CONFIG_TABLE_NAME: source_table,
|
|
51
62
|
consts.CONFIG_TARGET_SCHEMA_NAME: target_table_map[target_key][
|
|
52
63
|
consts.CONFIG_SCHEMA_NAME
|
|
53
64
|
],
|
|
@@ -95,6 +106,7 @@ def get_mapped_table_configs(
|
|
|
95
106
|
allowed_schemas: list = None,
|
|
96
107
|
include_views: bool = False,
|
|
97
108
|
score_cutoff: int = 1,
|
|
109
|
+
schema_map: dict = None,
|
|
98
110
|
) -> list:
|
|
99
111
|
"""Get table list from each client and match them together into a single list of dicts."""
|
|
100
112
|
source_table_map = _get_table_map(
|
|
@@ -102,7 +114,10 @@ def get_mapped_table_configs(
|
|
|
102
114
|
)
|
|
103
115
|
target_table_map = _get_table_map(target_client, include_views=include_views)
|
|
104
116
|
return _compare_match_tables(
|
|
105
|
-
source_table_map,
|
|
117
|
+
source_table_map,
|
|
118
|
+
target_table_map,
|
|
119
|
+
score_cutoff=score_cutoff,
|
|
120
|
+
schema_map=schema_map,
|
|
106
121
|
)
|
|
107
122
|
|
|
108
123
|
|
|
@@ -114,13 +129,22 @@ def find_tables_using_string_matching(args) -> str:
|
|
|
114
129
|
source_client = clients.get_data_client(mgr.get_connection_config(args.source_conn))
|
|
115
130
|
target_client = clients.get_data_client(mgr.get_connection_config(args.target_conn))
|
|
116
131
|
|
|
117
|
-
|
|
132
|
+
allowed_schemas_raw = cli_tools.get_arg_list(args.allowed_schemas) or []
|
|
133
|
+
allowed_schemas = []
|
|
134
|
+
schema_map = {}
|
|
135
|
+
for schema in allowed_schemas_raw:
|
|
136
|
+
if "=" in schema:
|
|
137
|
+
schema, target_schema = schema.split("=", 1)
|
|
138
|
+
schema_map[schema] = target_schema
|
|
139
|
+
allowed_schemas.append(schema)
|
|
140
|
+
|
|
118
141
|
table_configs = get_mapped_table_configs(
|
|
119
142
|
source_client,
|
|
120
143
|
target_client,
|
|
121
144
|
allowed_schemas=allowed_schemas,
|
|
122
145
|
include_views=args.include_views,
|
|
123
146
|
score_cutoff=score_cutoff,
|
|
147
|
+
schema_map=schema_map,
|
|
124
148
|
)
|
|
125
149
|
return json.dumps(table_configs)
|
|
126
150
|
|
|
@@ -23,7 +23,6 @@ import google.oauth2.service_account
|
|
|
23
23
|
from data_validation import clients, consts, exceptions, util
|
|
24
24
|
from data_validation.result_handlers.base_backend import BaseBackendResultHandler
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
BQRH_WRITE_MESSAGE = "Results written to BigQuery"
|
|
28
27
|
BQRH_NO_WRITE_MESSAGE = "No results to write to BigQuery"
|
|
29
28
|
|
|
@@ -20,7 +20,6 @@ import re
|
|
|
20
20
|
|
|
21
21
|
from data_validation import metadata, consts, clients, exceptions
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
# Check for decimal data type with precision and/or scale. Permits hyphen in p/s for value ranges.
|
|
25
24
|
DECIMAL_PRECISION_SCALE_PATTERN = re.compile(
|
|
26
25
|
r"([!]?decimal)\(([0-9\-]+)(?:,[ ]*([0-9\-]+))?\)", re.I
|
|
@@ -187,7 +186,10 @@ def schema_validation_matching(
|
|
|
187
186
|
)
|
|
188
187
|
else:
|
|
189
188
|
# Target data type mismatch
|
|
190
|
-
(
|
|
189
|
+
(
|
|
190
|
+
higher_precision,
|
|
191
|
+
lower_precision,
|
|
192
|
+
) = parse_n_validate_datatypes(
|
|
191
193
|
string_val(source_field_type), string_val(target_field_type)
|
|
192
194
|
)
|
|
193
195
|
if higher_precision:
|
|
@@ -16,6 +16,7 @@ import json
|
|
|
16
16
|
import logging
|
|
17
17
|
import re
|
|
18
18
|
import time
|
|
19
|
+
import uuid
|
|
19
20
|
|
|
20
21
|
from data_validation import clients, exceptions
|
|
21
22
|
|
|
@@ -87,3 +88,19 @@ def ibis_table_to_sql(ibis_table: "IbisTable", alchemy_client: "BaseBackend") ->
|
|
|
87
88
|
else:
|
|
88
89
|
sql_string = str(ibis_table.compile())
|
|
89
90
|
return sql_string
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def dvt_temp_object_name(prefix: str = "dvt_temp") -> str:
|
|
94
|
+
"""Generate a random name for when DVT needs to create a temporary object.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
prefix: The prefix to use for the temporary object name.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
A lower case random name for when DVT needs to create a temporary object.
|
|
101
|
+
"""
|
|
102
|
+
if not isinstance(prefix, str) or not re.match(r"^[a-zA-Z0-9_]+$", prefix):
|
|
103
|
+
raise exceptions.ValidationException(
|
|
104
|
+
f"Invalid prefix: '{prefix}'. Only alphanumeric and underscore characters are allowed."
|
|
105
|
+
)
|
|
106
|
+
return f"{prefix}_{uuid.uuid4().hex[:8].lower()}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: google-pso-data-validator
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.6.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
|
|
@@ -39,7 +39,7 @@ Requires-Dist: 1.10.11; extra == "apache-airflow"
|
|
|
39
39
|
Provides-Extra: pyspark
|
|
40
40
|
Requires-Dist: 3.0.0; extra == "pyspark"
|
|
41
41
|
Provides-Extra: develop
|
|
42
|
-
Requires-Dist: black==
|
|
42
|
+
Requires-Dist: black==26.1.0; extra == "develop"
|
|
43
43
|
Requires-Dist: flake8; extra == "develop"
|
|
44
44
|
Requires-Dist: freezegun; extra == "develop"
|
|
45
45
|
Requires-Dist: pyfakefs; extra == "develop"
|
|
@@ -719,17 +719,27 @@ target. The `find-tables` command:
|
|
|
719
719
|
- Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
|
|
720
720
|
- Pulls all tables from the target
|
|
721
721
|
- Uses Jaro Similarity algorithm to match tables
|
|
722
|
+
- Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
|
|
722
723
|
- Finally, it prints a JSON list of tables which can be a reference for the
|
|
723
724
|
validation run config.
|
|
724
725
|
|
|
725
726
|
Note that our default value for the `score-cutoff` parameter is 1 and it seeks for identical matches. If no matches occur, reduce this value as deemed necessary. By using smaller numbers such as 0.7, 0.65 etc you can get more matches. For reference, we make use of [this jaro_similarity method](https://jamesturk.github.io/jellyfish/functions/#jaro-similarity) for the string comparison.
|
|
726
727
|
|
|
728
|
+
|
|
727
729
|
```shell
|
|
728
730
|
data-validation find-tables --source-conn source --target-conn target \
|
|
729
731
|
--allowed-schemas pso_data_validator \
|
|
730
732
|
--score-cutoff 1
|
|
731
733
|
```
|
|
732
734
|
|
|
735
|
+
If your source and target environments have different schema names (e.g., `prod_raw` vs `dwh_raw`), you can provide a mapping directly in the `--allowed-schemas` flag using the `source_schema=target_schema` syntax.
|
|
736
|
+
|
|
737
|
+
```shell
|
|
738
|
+
data-validation find-tables --source-conn source --target-conn target \
|
|
739
|
+
--allowed-schemas prod=dwh \
|
|
740
|
+
--score-cutoff 1
|
|
741
|
+
```
|
|
742
|
+
|
|
733
743
|
### Using Beta CLI Features
|
|
734
744
|
|
|
735
745
|
There may be occasions we want to release a new CLI feature under a Beta flag.
|
|
@@ -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 = "8.
|
|
21
|
+
version = "8.6.0"
|
|
22
22
|
release_status = "Development Status :: 4 - Beta"
|
|
23
23
|
|
|
24
24
|
with open("README.md", "r") as fh:
|
|
@@ -51,7 +51,7 @@ extras_require = {
|
|
|
51
51
|
"apache-airflow": "1.10.11",
|
|
52
52
|
"pyspark": "3.0.0",
|
|
53
53
|
"develop": [
|
|
54
|
-
"black==
|
|
54
|
+
"black==26.1.0",
|
|
55
55
|
"flake8",
|
|
56
56
|
"freezegun",
|
|
57
57
|
"pyfakefs",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
from typing import Iterable, Optional
|
|
16
|
+
import functools
|
|
17
|
+
import inspect
|
|
16
18
|
|
|
17
19
|
from ibis.backends.base.sql.alchemy.datatypes import to_sqla_type
|
|
18
20
|
import ibis.expr.datatypes as dt
|
|
@@ -174,6 +176,9 @@ def db2_type_string_length(
|
|
|
174
176
|
return 19
|
|
175
177
|
elif ibis_column.is_integer():
|
|
176
178
|
return ibis_integer_string_length(ibis_column)
|
|
179
|
+
elif ibis_column.is_binary() and raw_data_type:
|
|
180
|
+
# Position 1 in raw_types is the data length.
|
|
181
|
+
return raw_data_type[1]
|
|
177
182
|
else:
|
|
178
183
|
return None
|
|
179
184
|
|
|
@@ -196,3 +201,42 @@ def ibis_integer_string_length(ibis_column: dt.DataType) -> Optional[int]:
|
|
|
196
201
|
|
|
197
202
|
|
|
198
203
|
Value.force_cast = force_cast
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def cache_generator_results(func):
|
|
207
|
+
"""Decorator to cache generator results at the instance level."""
|
|
208
|
+
# Pre-compute the function signature to avoid doing this on every call
|
|
209
|
+
sig = inspect.signature(func)
|
|
210
|
+
|
|
211
|
+
@functools.wraps(func)
|
|
212
|
+
def wrapper(self, *args, **kwargs):
|
|
213
|
+
# Create an instance-level cache if it doesn't already exist.
|
|
214
|
+
# This prevents instances from sharing cache state and avoids memory leaks.
|
|
215
|
+
if not hasattr(self, "_generator_cache"):
|
|
216
|
+
self._generator_cache = {}
|
|
217
|
+
|
|
218
|
+
# Bind the provided args and kwargs to the function's signature.
|
|
219
|
+
# This creates a unified representation of the arguments, avoiding cache misses
|
|
220
|
+
# when identical values are passed differently (e.g., positional vs. keyword).
|
|
221
|
+
bound_args = sig.bind(self, *args, **kwargs)
|
|
222
|
+
bound_args.apply_defaults()
|
|
223
|
+
|
|
224
|
+
# Remove 'self' from the arguments since the cache is already scoped to the instance
|
|
225
|
+
arguments = bound_args.arguments.copy()
|
|
226
|
+
arguments.pop("self", None)
|
|
227
|
+
|
|
228
|
+
# Ensure only supported types are used to prevent frozenset failures on unhashable types
|
|
229
|
+
for v in arguments.values():
|
|
230
|
+
assert isinstance(
|
|
231
|
+
v, (str, int, type(None))
|
|
232
|
+
), f"Unsupported argument type in cache_generator_results: {type(v)}"
|
|
233
|
+
|
|
234
|
+
# Generate a unique cache key based on the function name and the canonical arguments
|
|
235
|
+
key = (func.__name__, frozenset(arguments.items()))
|
|
236
|
+
if key not in self._generator_cache:
|
|
237
|
+
# Fully evaluate the generator to a list to store it in the cache
|
|
238
|
+
self._generator_cache[key] = list(func(self, *args, **kwargs))
|
|
239
|
+
|
|
240
|
+
yield from self._generator_cache[key]
|
|
241
|
+
|
|
242
|
+
return wrapper
|
|
@@ -22,6 +22,7 @@ extended its own registry. Eventually this can potentially be pushed to
|
|
|
22
22
|
Ibis as an override, though it would not apply for Pandas and other
|
|
23
23
|
non-textual languages.
|
|
24
24
|
"""
|
|
25
|
+
|
|
25
26
|
import datetime
|
|
26
27
|
import dateutil
|
|
27
28
|
import numpy as np
|
|
@@ -508,19 +509,19 @@ if OracleExprTranslator:
|
|
|
508
509
|
ops.StringLength
|
|
509
510
|
]
|
|
510
511
|
|
|
511
|
-
PostgreSQLExprTranslator._registry[
|
|
512
|
-
|
|
513
|
-
|
|
512
|
+
PostgreSQLExprTranslator._registry[ops.HashBytes] = (
|
|
513
|
+
postgres_registry.sa_format_hashbytes
|
|
514
|
+
)
|
|
514
515
|
PostgreSQLExprTranslator._registry[RawSQL] = sa_format_raw_sql
|
|
515
516
|
PostgreSQLExprTranslator._registry[ToChar] = sa_format_to_char
|
|
516
517
|
PostgreSQLExprTranslator._registry[ops.Cast] = postgres_registry.sa_cast_postgres
|
|
517
518
|
PostgreSQLExprTranslator._registry[BinaryLength] = sa_format_binary_length
|
|
518
|
-
PostgreSQLExprTranslator._registry[
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
PostgreSQLExprTranslator._registry[
|
|
522
|
-
|
|
523
|
-
|
|
519
|
+
PostgreSQLExprTranslator._registry[ops.ExtractEpochSeconds] = (
|
|
520
|
+
postgres_registry.sa_epoch_seconds
|
|
521
|
+
)
|
|
522
|
+
PostgreSQLExprTranslator._registry[PaddedCharLength] = (
|
|
523
|
+
postgres_registry.sa_format_postgres_padded_char_length
|
|
524
|
+
)
|
|
524
525
|
|
|
525
526
|
|
|
526
527
|
MsSqlExprTranslator._registry[ops.HashBytes] = mssql_registry.sa_format_hashbytes
|
|
@@ -568,9 +569,9 @@ if TeradataExprTranslator:
|
|
|
568
569
|
TeradataExprTranslator._registry[RawSQL] = format_raw_sql
|
|
569
570
|
TeradataExprTranslator._registry[ops.HashBytes] = format_hashbytes_teradata
|
|
570
571
|
TeradataExprTranslator._registry[BinaryLength] = sa_format_binary_length
|
|
571
|
-
TeradataExprTranslator._registry[
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
TeradataExprTranslator._registry[PaddedCharLength] = (
|
|
573
|
+
TeradataExprTranslator._registry[ops.StringLength]
|
|
574
|
+
)
|
|
574
575
|
|
|
575
576
|
if SnowflakeExprTranslator:
|
|
576
577
|
SnowflakeExprTranslator._registry[ops.Cast] = sa_cast_snowflake
|
|
@@ -583,10 +584,10 @@ if SnowflakeExprTranslator:
|
|
|
583
584
|
SnowflakeExprTranslator._registry[ops.RStrip] = _sa_whitespace_rstrip
|
|
584
585
|
|
|
585
586
|
if SybaseExprTranslator:
|
|
586
|
-
SybaseExprTranslator._registry[
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
SybaseExprTranslator._registry[BinaryLength] = (
|
|
588
|
+
mssql_registry.sa_format_binary_length
|
|
589
|
+
)
|
|
589
590
|
SybaseExprTranslator._registry[RawSQL] = sa_format_raw_sql
|
|
590
|
-
SybaseExprTranslator._registry[
|
|
591
|
-
|
|
592
|
-
|
|
591
|
+
SybaseExprTranslator._registry[PaddedCharLength] = (
|
|
592
|
+
mssql_registry.sa_format_string_length
|
|
593
|
+
)
|