google-pso-data-validator 7.6.0__tar.gz → 7.7.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.
Files changed (95) hide show
  1. {google_pso_data_validator-7.6.0/google_pso_data_validator.egg-info → google_pso_data_validator-7.7.0}/PKG-INFO +10 -3
  2. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/README.md +9 -2
  3. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/cli_tools.py +9 -2
  4. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/clients.py +27 -7
  5. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/combiner.py +12 -12
  6. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/config_manager.py +19 -13
  7. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/consts.py +18 -0
  8. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/query_builder/query_builder.py +9 -1
  9. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/bigquery.py +1 -1
  10. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/validation_builder.py +63 -11
  11. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0/google_pso_data_validator.egg-info}/PKG-INFO +10 -3
  12. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/SOURCES.txt +2 -2
  13. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/setup.py +1 -1
  14. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_addon/operations.py +44 -63
  15. google_pso_data_validator-7.7.0/third_party/ibis/ibis_bigquery/__init__.py +142 -0
  16. google_pso_data_validator-7.7.0/third_party/ibis/ibis_bigquery/api.py +35 -0
  17. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_postgres/client.py +7 -0
  18. google_pso_data_validator-7.7.0/third_party/ibis/ibis_postgres/registry.py +86 -0
  19. google_pso_data_validator-7.6.0/third_party/ibis/ibis_biquery/api.py +0 -15
  20. google_pso_data_validator-7.6.0/third_party/ibis/ibis_postgres/registry.py +0 -34
  21. google_pso_data_validator-7.6.0/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  22. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/LICENSE +0 -0
  23. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/__init__.py +0 -0
  24. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/__main__.py +0 -0
  25. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/app.py +0 -0
  26. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/client_info.py +0 -0
  27. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/data_validation.py +0 -0
  28. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/exceptions.py +0 -0
  29. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/find_tables.py +0 -0
  30. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/gcs_helper.py +0 -0
  31. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/jellyfish_distance.py +0 -0
  32. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/metadata.py +0 -0
  33. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/partition_builder.py +0 -0
  34. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/query_builder/__init__.py +0 -0
  35. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  36. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/query_builder/random_row_builder.py +0 -0
  37. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/raw_query.py +0 -0
  38. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/__init__.py +0 -0
  39. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/base_backend.py +0 -0
  40. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/factory.py +0 -0
  41. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/postgres.py +0 -0
  42. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/text.py +0 -0
  43. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/schema_validation.py +0 -0
  44. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/secret_manager.py +0 -0
  45. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/state_manager.py +0 -0
  46. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/data_validation/util.py +0 -0
  47. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  48. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  49. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/requires.txt +0 -0
  50. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  51. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/setup.cfg +0 -0
  52. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  53. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_addon/api.py +0 -0
  54. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  55. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  56. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  57. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
  58. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  59. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  60. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  61. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  62. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  63. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
  64. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/api.py +0 -0
  65. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  66. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
  67. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/registry.py +0 -0
  68. {google_pso_data_validator-7.6.0/third_party/ibis/ibis_biquery → google_pso_data_validator-7.7.0/third_party/ibis/ibis_impala}/__init__.py +0 -0
  69. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_impala/api.py +0 -0
  70. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
  71. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  72. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  73. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
  74. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  75. {google_pso_data_validator-7.6.0/third_party/ibis/ibis_impala → google_pso_data_validator-7.7.0/third_party/ibis/ibis_mysql/base_sql_compiler}/__init__.py +0 -0
  76. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  77. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  78. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
  79. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  80. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  81. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
  82. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  83. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  84. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  85. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
  86. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  87. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  88. {google_pso_data_validator-7.6.0/third_party/ibis/ibis_mysql/base_sql_compiler → google_pso_data_validator-7.7.0/third_party/ibis/ibis_snowflake}/__init__.py +0 -0
  89. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  90. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  91. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
  92. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  93. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  94. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  95. {google_pso_data_validator-7.6.0 → google_pso_data_validator-7.7.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: 7.6.0
3
+ Version: 7.7.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
@@ -125,8 +125,10 @@ please review the [Connections](https://github.com/GoogleCloudPlatform/professio
125
125
  ### Running Validations
126
126
 
127
127
  The CLI is the main interface to use this tool and it has several different
128
- commands which can be used to create and run validations. Below are the command
129
- syntax and options for running validations.
128
+ commands which can be used to create and run validations. DVT is designed to run in
129
+ an environment connected to GCP services, specifically, BigQuery, GCS and Secret manager.
130
+ If DVT is being run on-premises or in an environment with restricted access to GCP services, see
131
+ [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
130
132
 
131
133
  Alternatives to running DVT in the CLI include deploying DVT to Cloud Run, Cloud Functions, or Airflow
132
134
  ([Examples Here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples)). See the [Validation Logic](https://github.com/GoogleCloudPlatform/professional-services-data-validator#validation-logic) section
@@ -584,6 +586,11 @@ For example, this flag can be used as follows:
584
586
  "target_query": "SELECT `hash__all`, `station_id`\nFROM ..."
585
587
  }
586
588
  ```
589
+ #### Running DVT at on-prem
590
+ On-premises environments can have limited access to GCP services. DVT supports using BigQuery for storing validation results, GCS for storage and
591
+ the Secret Manager for secrets. You may also use BigQuery and Spanner as a source or target for validation. Service
592
+ APIs (i.e. bigquery.googleapis.com) may not always be accessible due to firewall restrictions. Work with your network
593
+ adminstrator to identify the way to access these services. They may set up a [Private Service Connect Endpoint](https://cloud.google.com/vpc/docs/about-accessing-google-apis-endpoints). DVT supports accessing source and target tables in Spanner and BigQuery via endpoints set up in your network. Connection Parameters for [Spanner](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-spanner) and [BigQuery](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-bigquery) outline regarding how to specify endpoints.
587
594
 
588
595
  ### Running DVT with YAML Configuration Files
589
596
 
@@ -66,8 +66,10 @@ please review the [Connections](https://github.com/GoogleCloudPlatform/professio
66
66
  ### Running Validations
67
67
 
68
68
  The CLI is the main interface to use this tool and it has several different
69
- commands which can be used to create and run validations. Below are the command
70
- syntax and options for running validations.
69
+ commands which can be used to create and run validations. DVT is designed to run in
70
+ an environment connected to GCP services, specifically, BigQuery, GCS and Secret manager.
71
+ If DVT is being run on-premises or in an environment with restricted access to GCP services, see
72
+ [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
71
73
 
72
74
  Alternatives to running DVT in the CLI include deploying DVT to Cloud Run, Cloud Functions, or Airflow
73
75
  ([Examples Here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples)). See the [Validation Logic](https://github.com/GoogleCloudPlatform/professional-services-data-validator#validation-logic) section
@@ -525,6 +527,11 @@ For example, this flag can be used as follows:
525
527
  "target_query": "SELECT `hash__all`, `station_id`\nFROM ..."
526
528
  }
527
529
  ```
530
+ #### Running DVT at on-prem
531
+ On-premises environments can have limited access to GCP services. DVT supports using BigQuery for storing validation results, GCS for storage and
532
+ the Secret Manager for secrets. You may also use BigQuery and Spanner as a source or target for validation. Service
533
+ APIs (i.e. bigquery.googleapis.com) may not always be accessible due to firewall restrictions. Work with your network
534
+ adminstrator to identify the way to access these services. They may set up a [Private Service Connect Endpoint](https://cloud.google.com/vpc/docs/about-accessing-google-apis-endpoints). DVT supports accessing source and target tables in Spanner and BigQuery via endpoints set up in your network. Connection Parameters for [Spanner](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-spanner) and [BigQuery](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-bigquery) outline regarding how to specify endpoints.
528
535
 
529
536
  ### Running DVT with YAML Configuration Files
530
537
 
@@ -76,7 +76,11 @@ CONNECTION_SOURCE_FIELDS = {
76
76
  ["google_service_account_key_path", "(Optional) GCP SA Key Path"],
77
77
  [
78
78
  "api_endpoint",
79
- '(Optional) GCP BigQuery API endpoint (e.g. "https://mybq.p.googleapis.com")',
79
+ '(Optional) GCP BigQuery API endpoint (e.g. "https://bigquery-mypsc.p.googleapis.com")',
80
+ ],
81
+ [
82
+ "storage_api_endpoint",
83
+ '(Optional) GCP BigQuery Storage API endpoint (e.g. "https://bigquerystorage-mypsc.p.googleapis.com")',
80
84
  ],
81
85
  ],
82
86
  consts.SOURCE_TYPE_TERADATA: [
@@ -140,7 +144,7 @@ CONNECTION_SOURCE_FIELDS = {
140
144
  ["google_service_account_key_path", "(Optional) GCP SA Key Path"],
141
145
  [
142
146
  "api_endpoint",
143
- '(Optional) GCP Spanner API endpoint (e.g. "https://mycs.p.googleapis.com")',
147
+ '(Optional) GCP Spanner API endpoint (e.g. "https://spanner-mypsc.p.googleapis.com")',
144
148
  ],
145
149
  ],
146
150
  consts.SOURCE_TYPE_FILESYSTEM: [
@@ -1284,6 +1288,9 @@ def _get_result_handler(rc_value: str, sa_file=None) -> dict:
1284
1288
  consts.PROJECT_ID: conn_from_file["project_id"],
1285
1289
  consts.TABLE_ID: config[1],
1286
1290
  consts.API_ENDPOINT: conn_from_file.get("api_endpoint", None),
1291
+ consts.STORAGE_API_ENDPOINT: conn_from_file.get(
1292
+ "storage_api_endpoint", None
1293
+ ),
1287
1294
  }
1288
1295
  elif conn_from_file[consts.SOURCE_TYPE] == consts.SOURCE_TYPE_POSTGRES:
1289
1296
  result_handler = {
@@ -27,6 +27,8 @@ import pandas
27
27
 
28
28
  from data_validation import client_info, consts, exceptions
29
29
  from data_validation.secret_manager import SecretManagerBuilder
30
+
31
+ from third_party.ibis.ibis_bigquery.api import bigquery_connect
30
32
  from third_party.ibis.ibis_cloud_spanner.api import spanner_connect
31
33
  from third_party.ibis.ibis_impala.api import impala_connect
32
34
  from third_party.ibis.ibis_mssql.api import mssql_connect
@@ -111,24 +113,42 @@ def get_google_bigquery_client(
111
113
  )
112
114
 
113
115
 
116
+ def _get_google_bqstorage_client(credentials=None, api_endpoint: str = None):
117
+ options = None
118
+ if api_endpoint:
119
+ options = client_options.ClientOptions(api_endpoint=api_endpoint)
120
+ from google.cloud import bigquery_storage_v1 as bigquery_storage
121
+
122
+ return bigquery_storage.BigQueryReadClient(
123
+ credentials=credentials,
124
+ client_options=options,
125
+ )
126
+
127
+
114
128
  def get_bigquery_client(
115
- project_id: str, dataset_id: str = "", credentials=None, api_endpoint: str = None
129
+ project_id: str,
130
+ dataset_id: str = "",
131
+ credentials=None,
132
+ api_endpoint: str = None,
133
+ storage_api_endpoint: str = None,
116
134
  ):
117
135
  google_client = get_google_bigquery_client(
118
136
  project_id, credentials=credentials, api_endpoint=api_endpoint
119
137
  )
138
+ bqstorage_client = None
139
+ if storage_api_endpoint:
140
+ bqstorage_client = _get_google_bqstorage_client(
141
+ credentials=credentials, api_endpoint=storage_api_endpoint
142
+ )
120
143
 
121
- ibis_client = ibis.bigquery.connect(
144
+ return bigquery_connect(
122
145
  project_id=project_id,
123
146
  dataset_id=dataset_id,
124
147
  credentials=credentials,
148
+ bigquery_client=google_client,
149
+ bqstorage_client=bqstorage_client,
125
150
  )
126
151
 
127
- # Override the BigQuery client object to ensure the correct user agent is
128
- # included and any api_endpoint is used.
129
- ibis_client.client = google_client
130
- return ibis_client
131
-
132
152
 
133
153
  def get_pandas_client(table_name, file_path, file_type):
134
154
  """Return pandas client and env with file loaded into DataFrame
@@ -406,18 +406,18 @@ def _pivot_result(
406
406
  consts.AGGREGATION_TYPE
407
407
  ),
408
408
  ibis.literal(validation.get_table_name(result_type)).name(
409
- "table_name"
409
+ consts.COMBINER_TABLE_NAME
410
410
  ),
411
411
  # Cast to string to ensure types match, even when column
412
412
  # name is NULL (such as for count aggregations).
413
413
  ibis.literal(validation.get_column_name(result_type))
414
414
  .cast("string")
415
- .name("column_name"),
415
+ .name(consts.COMBINER_COLUMN_NAME),
416
416
  primary_keys,
417
417
  ibis.literal(validation.num_random_rows).name(
418
418
  consts.NUM_RANDOM_ROWS
419
419
  ),
420
- result[field].cast("string").name("agg_value"),
420
+ result[field].cast("string").name(consts.COMBINER_AGG_VALUE),
421
421
  )
422
422
  + join_on_fields
423
423
  )
@@ -469,11 +469,11 @@ def _join_pivots(
469
469
  + [
470
470
  source[consts.VALIDATION_TYPE],
471
471
  source[consts.AGGREGATION_TYPE],
472
- source[consts.CONFIG_TABLE_NAME],
473
- source["column_name"],
472
+ source[consts.COMBINER_TABLE_NAME],
473
+ source[consts.COMBINER_COLUMN_NAME],
474
474
  source[consts.CONFIG_PRIMARY_KEYS],
475
475
  source[consts.NUM_RANDOM_ROWS],
476
- source["agg_value"],
476
+ source[consts.COMBINER_AGG_VALUE],
477
477
  differences[consts.VALIDATION_DIFFERENCE],
478
478
  differences[consts.VALIDATION_PCT_DIFFERENCE],
479
479
  differences[consts.VALIDATION_PCT_THRESHOLD],
@@ -488,12 +488,12 @@ def _join_pivots(
488
488
  source_difference[consts.AGGREGATION_TYPE]
489
489
  .fillna(target[consts.AGGREGATION_TYPE])
490
490
  .name(consts.AGGREGATION_TYPE),
491
- source_difference["table_name"].name(consts.SOURCE_TABLE_NAME),
492
- source_difference["column_name"].name(consts.SOURCE_COLUMN_NAME),
493
- source_difference["agg_value"].name(consts.SOURCE_AGG_VALUE),
494
- target["table_name"].name(consts.TARGET_TABLE_NAME),
495
- target["column_name"].name(consts.TARGET_COLUMN_NAME),
496
- target["agg_value"].name(consts.TARGET_AGG_VALUE),
491
+ source_difference[consts.COMBINER_TABLE_NAME].name(consts.SOURCE_TABLE_NAME),
492
+ source_difference[consts.COMBINER_COLUMN_NAME].name(consts.SOURCE_COLUMN_NAME),
493
+ source_difference[consts.COMBINER_AGG_VALUE].name(consts.SOURCE_AGG_VALUE),
494
+ target[consts.COMBINER_TABLE_NAME].name(consts.TARGET_TABLE_NAME),
495
+ target[consts.COMBINER_COLUMN_NAME].name(consts.TARGET_COLUMN_NAME),
496
+ target[consts.COMBINER_AGG_VALUE].name(consts.TARGET_AGG_VALUE),
497
497
  group_by_columns,
498
498
  source_difference[consts.CONFIG_PRIMARY_KEYS],
499
499
  source_difference[consts.NUM_RANDOM_ROWS],
@@ -611,7 +611,7 @@ class ConfigManager(object):
611
611
  self.build_config_calculated_fields(
612
612
  [casefold_source_columns[field.casefold()]],
613
613
  [casefold_target_columns[field.casefold()]],
614
- "rstrip",
614
+ consts.CALC_FIELD_RSTRIP,
615
615
  alias,
616
616
  0,
617
617
  )
@@ -812,16 +812,16 @@ class ConfigManager(object):
812
812
  consts.CONFIG_FIELD_ALIAS
813
813
  ]
814
814
  depth = 1
815
- calc_func = "length"
815
+ calc_func = consts.CALC_FIELD_LENGTH
816
816
  elif column_type in ["string", "!string"]:
817
- calc_func = "length"
817
+ calc_func = consts.CALC_FIELD_LENGTH
818
818
 
819
819
  elif self._is_uuid(column_type, target_column_type):
820
820
  calc_func = consts.CONFIG_CAST
821
821
  cast_type = consts.CONFIG_CAST_UUID_STRING
822
822
 
823
823
  elif column_type in ["binary", "!binary"]:
824
- calc_func = "byte_length"
824
+ calc_func = consts.CALC_FIELD_BYTE_LENGTH
825
825
 
826
826
  elif column_type in ["timestamp", "!timestamp", "date", "!date"]:
827
827
  if (
@@ -841,7 +841,7 @@ class ConfigManager(object):
841
841
  ]
842
842
  depth = 1
843
843
 
844
- calc_func = "epoch_seconds"
844
+ calc_func = consts.CALC_FIELD_EPOCH_SECONDS
845
845
  if agg_type == consts.CONFIG_TYPE_SUM:
846
846
  # It is possible to exceed int64 when summing epoch_seconds therefore cast to string.
847
847
  # See issue 1391 for details.
@@ -1138,7 +1138,7 @@ class ConfigManager(object):
1138
1138
  target_table: "ibis.expr.types.Table",
1139
1139
  ) -> dict:
1140
1140
  """Mutates col_config to contain any overrides. Also returns col_config for convenience."""
1141
- if col_config["calc_type"] != "cast":
1141
+ if col_config["calc_type"] != consts.CALC_FIELD_CAST:
1142
1142
  return col_config
1143
1143
 
1144
1144
  source_table_schema = {k: v for k, v in source_table.schema().items()}
@@ -1186,13 +1186,19 @@ class ConfigManager(object):
1186
1186
 
1187
1187
  def _get_order_of_operations(self, calc_type: str) -> List[str]:
1188
1188
  """Return order of operations for row validation."""
1189
- order_of_operations = ["cast", "ifnull", "rstrip"]
1189
+ order_of_operations = [
1190
+ consts.CALC_FIELD_CAST,
1191
+ consts.CALC_FIELD_IFNULL,
1192
+ consts.CALC_FIELD_RSTRIP,
1193
+ ]
1190
1194
  if self.case_insensitive_match():
1191
- order_of_operations.append("upper")
1192
- if calc_type == "hash":
1193
- order_of_operations.extend(["concat", "hash"])
1194
- elif calc_type == "concat":
1195
- order_of_operations.append("concat")
1195
+ order_of_operations.append(consts.CALC_FIELD_UPPER)
1196
+ if calc_type == consts.CALC_FIELD_HASH:
1197
+ order_of_operations.extend(
1198
+ [consts.CALC_FIELD_CONCAT, consts.CALC_FIELD_HASH]
1199
+ )
1200
+ elif calc_type == consts.CALC_FIELD_CONCAT:
1201
+ order_of_operations.append(consts.CALC_FIELD_CONCAT)
1196
1202
 
1197
1203
  return order_of_operations
1198
1204
 
@@ -1241,7 +1247,7 @@ class ConfigManager(object):
1241
1247
  previous_level = [x for x in casefold_source_columns.keys()]
1242
1248
  else:
1243
1249
  previous_level = [k for k, v in column_aliases.items() if v == i - 1]
1244
- if calc in ["concat", "hash"]:
1250
+ if calc in [consts.CALC_FIELD_CONCAT, consts.CALC_FIELD_HASH]:
1245
1251
  col = {}
1246
1252
  col["source_reference"] = previous_level
1247
1253
  col["target_reference"] = previous_level
@@ -143,6 +143,7 @@ PROJECT_ID = "project_id"
143
143
  TABLE_ID = "table_id"
144
144
  GOOGLE_SERVICE_ACCOUNT_KEY_PATH = "google_service_account_key_path"
145
145
  API_ENDPOINT = "api_endpoint"
146
+ STORAGE_API_ENDPOINT = "storage_api_endpoint"
146
147
 
147
148
  # Result Handler Output Table Fields
148
149
  VALIDATION_TYPE = "validation_type"
@@ -181,6 +182,11 @@ FAILED_SOURCE_NOT_IN_TARGET = "failed_rows_present_in_source_not_in_target"
181
182
  FAILED_TARGET_NOT_IN_SOURCE = "failed_rows_present_in_target_not_in_source"
182
183
  FAILED_PRESENT_IN_BOTH_TABLES = "failed_rows_present_in_both_source_and_target"
183
184
 
185
+ # Combiner only constants
186
+ COMBINER_TABLE_NAME = "dvt_table_name"
187
+ COMBINER_COLUMN_NAME = "dvt_column_name"
188
+ COMBINER_AGG_VALUE = "dvt_agg_value"
189
+
184
190
  # SQL Template Formatting
185
191
  # TODO: should this be managed in query_builder if that is the only place its used?
186
192
  COUNT_STAR = "{count_star}"
@@ -239,3 +245,15 @@ MAX_CONCAT_COLUMNS_DEFAULTS = {
239
245
  # Minimizing risk of: [Error 3556] Too many columns defined for this table.
240
246
  "teradata": 500,
241
247
  }
248
+
249
+ # CalculatedField expression constants.
250
+ CALC_FIELD_CAST = "cast"
251
+ CALC_FIELD_CONCAT = "concat"
252
+ CALC_FIELD_BYTE_LENGTH = "byte_length"
253
+ CALC_FIELD_EPOCH_SECONDS = "epoch_seconds"
254
+ CALC_FIELD_HASH = "hash"
255
+ CALC_FIELD_IFNULL = "ifnull"
256
+ CALC_FIELD_LENGTH = "length"
257
+ CALC_FIELD_PADDED_CHAR_LENGTH = "padded_char_length"
258
+ CALC_FIELD_RSTRIP = "rstrip"
259
+ CALC_FIELD_UPPER = "upper"
@@ -358,6 +358,14 @@ class CalculatedField(object):
358
358
  fields,
359
359
  )
360
360
 
361
+ @staticmethod
362
+ def padded_char_length(config, fields):
363
+ return CalculatedField(
364
+ ibis.expr.types.StringValue.padded_char_length,
365
+ config,
366
+ fields,
367
+ )
368
+
361
369
  @staticmethod
362
370
  def byte_length(config, fields):
363
371
  return CalculatedField(
@@ -403,7 +411,7 @@ class CalculatedField(object):
403
411
  @staticmethod
404
412
  def custom(config, fields):
405
413
  """Returns a CalculatedField instance built for any custom ibis expression
406
- i.e. 'ibis.expr.api.StringValue.replace'. For a list of supported functions,
414
+ e.g. 'ibis.expr.api.StringValue.replace'. For a list of supported functions,
407
415
  see https://github.com/ibis-project/ibis/blob/1.4.0/ibis/expr/api.py
408
416
  Args:
409
417
  expr (Str): A custom ibis expression to be used as a calc field
@@ -65,7 +65,7 @@ class BigQueryResultHandler(BaseBackendResultHandler):
65
65
  Explicit credentials to use in case default credentials
66
66
  aren't working properly.
67
67
  status_list (list): provided status to filter the results with
68
- api_endpoint (str): BigQuery API endpoint (e.g. https://mybq.p.googleapis.com)
68
+ api_endpoint (str): BigQuery API endpoint (e.g. https://bigquery-mypsc.p.googleapis.com)
69
69
  text_format (str, optional):
70
70
  This allows the user to influence the text results written via logger.debug.
71
71
  See: https://github.com/GoogleCloudPlatform/professional-services-data-validator/issues/871
@@ -11,8 +11,9 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- import logging
15
14
  from copy import deepcopy
15
+ import logging
16
+ from typing import TYPE_CHECKING
16
17
 
17
18
  from data_validation import consts, metadata
18
19
  from data_validation.clients import get_max_in_list_size
@@ -25,6 +26,9 @@ from data_validation.query_builder.query_builder import (
25
26
  QueryBuilder,
26
27
  )
27
28
 
29
+ if TYPE_CHECKING:
30
+ import ibis
31
+
28
32
 
29
33
  def list_to_sublists(id_list: list, max_size: int) -> list:
30
34
  """Return a list of items as a list of lists based on a max list length of max_size."""
@@ -87,6 +91,23 @@ class ValidationBuilder(object):
87
91
  else:
88
92
  return FilterField.isin(column_name, in_list)
89
93
 
94
+ def _is_padded_char(
95
+ self,
96
+ client: "ibis.backends.base.BaseBackend",
97
+ raw_column_metadata: dict,
98
+ column_name: str,
99
+ ) -> bool:
100
+ # Clients only need to implement _is_char_type_padded method if they support padded strings.
101
+ return (
102
+ (
103
+ client.is_char_type_padded(raw_column_metadata[column_name])
104
+ if raw_column_metadata.get(column_name)
105
+ else False
106
+ )
107
+ if hasattr(client, "is_char_type_padded") and raw_column_metadata
108
+ else False
109
+ )
110
+
90
111
  def clone(self):
91
112
  cloned_builder = ValidationBuilder(self.config_manager)
92
113
 
@@ -347,32 +368,63 @@ class ValidationBuilder(object):
347
368
  threshold=self.config_manager.threshold,
348
369
  )
349
370
 
350
- def add_calc(self, calc_field):
371
+ def _get_calc_type(
372
+ self,
373
+ calc_field: dict,
374
+ column_name: str,
375
+ client: "ibis.backends.base.BaseBackend",
376
+ raw_data_types: dict,
377
+ ) -> str:
378
+ if calc_field[
379
+ consts.CONFIG_TYPE
380
+ ] == consts.CALC_FIELD_LENGTH and self._is_padded_char(
381
+ client,
382
+ raw_data_types,
383
+ column_name,
384
+ ):
385
+ calc_type = consts.CALC_FIELD_PADDED_CHAR_LENGTH
386
+ else:
387
+ calc_type = calc_field[consts.CONFIG_TYPE]
388
+ # Check if valid calc field and return correct object.
389
+ if not hasattr(CalculatedField, calc_type):
390
+ raise Exception("Unknown Calculation Type: {}".format(calc_type))
391
+ return calc_type
392
+
393
+ def add_calc(self, calc_field: dict):
351
394
  """Add CalculatedField to Queries
352
395
 
353
396
  Args:
354
397
  calc_field (Dict): An object with source, target, and cast info
355
398
  """
356
- # prepare source and target payloads
399
+ # Prepare source and target payloads
357
400
  source_config = deepcopy(calc_field)
358
401
  source_fields = calc_field[consts.CONFIG_CALCULATED_SOURCE_COLUMNS]
359
402
  target_config = deepcopy(calc_field)
360
403
  target_fields = calc_field[consts.CONFIG_CALCULATED_TARGET_COLUMNS]
361
- # grab calc field metadata
404
+ # Grab calc field metadata
362
405
  alias = calc_field[consts.CONFIG_FIELD_ALIAS]
363
- calc_type = calc_field[consts.CONFIG_TYPE]
364
- # check if valid calc field and return correct object
365
- if not hasattr(CalculatedField, calc_type):
366
- raise Exception("Unknown Calculation Type: {}".format(calc_type))
367
- source_field = getattr(CalculatedField, calc_type)(
406
+ source_calc_type = self._get_calc_type(
407
+ calc_field,
408
+ source_fields[0],
409
+ self.source_client,
410
+ self.config_manager.get_source_raw_data_types(),
411
+ )
412
+ target_calc_type = self._get_calc_type(
413
+ calc_field,
414
+ target_fields[0],
415
+ self.target_client,
416
+ self.config_manager.get_target_raw_data_types(),
417
+ )
418
+
419
+ source_field = getattr(CalculatedField, source_calc_type)(
368
420
  config=source_config, fields=source_fields
369
421
  )
370
- target_field = getattr(CalculatedField, calc_type)(
422
+ target_field = getattr(CalculatedField, target_calc_type)(
371
423
  config=target_config, fields=target_fields
372
424
  )
373
425
  self.source_builder.add_calculated_field(source_field)
374
426
  self.target_builder.add_calculated_field(target_field)
375
- # register calc field under alias
427
+ # Register calc field under alias
376
428
  self.calculated_aliases[alias] = calc_field
377
429
 
378
430
  def get_source_query(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-pso-data-validator
3
- Version: 7.6.0
3
+ Version: 7.7.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
@@ -125,8 +125,10 @@ please review the [Connections](https://github.com/GoogleCloudPlatform/professio
125
125
  ### Running Validations
126
126
 
127
127
  The CLI is the main interface to use this tool and it has several different
128
- commands which can be used to create and run validations. Below are the command
129
- syntax and options for running validations.
128
+ commands which can be used to create and run validations. DVT is designed to run in
129
+ an environment connected to GCP services, specifically, BigQuery, GCS and Secret manager.
130
+ If DVT is being run on-premises or in an environment with restricted access to GCP services, see
131
+ [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
130
132
 
131
133
  Alternatives to running DVT in the CLI include deploying DVT to Cloud Run, Cloud Functions, or Airflow
132
134
  ([Examples Here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples)). See the [Validation Logic](https://github.com/GoogleCloudPlatform/professional-services-data-validator#validation-logic) section
@@ -584,6 +586,11 @@ For example, this flag can be used as follows:
584
586
  "target_query": "SELECT `hash__all`, `station_id`\nFROM ..."
585
587
  }
586
588
  ```
589
+ #### Running DVT at on-prem
590
+ On-premises environments can have limited access to GCP services. DVT supports using BigQuery for storing validation results, GCS for storage and
591
+ the Secret Manager for secrets. You may also use BigQuery and Spanner as a source or target for validation. Service
592
+ APIs (i.e. bigquery.googleapis.com) may not always be accessible due to firewall restrictions. Work with your network
593
+ adminstrator to identify the way to access these services. They may set up a [Private Service Connect Endpoint](https://cloud.google.com/vpc/docs/about-accessing-google-apis-endpoints). DVT supports accessing source and target tables in Spanner and BigQuery via endpoints set up in your network. Connection Parameters for [Spanner](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-spanner) and [BigQuery](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-bigquery) outline regarding how to specify endpoints.
587
594
 
588
595
  ### Running DVT with YAML Configuration Files
589
596
 
@@ -43,8 +43,8 @@ google_pso_data_validator.egg-info/top_level.txt
43
43
  third_party/ibis/ibis_addon/__init__.py
44
44
  third_party/ibis/ibis_addon/api.py
45
45
  third_party/ibis/ibis_addon/operations.py
46
- third_party/ibis/ibis_biquery/__init__.py
47
- third_party/ibis/ibis_biquery/api.py
46
+ third_party/ibis/ibis_bigquery/__init__.py
47
+ third_party/ibis/ibis_bigquery/api.py
48
48
  third_party/ibis/ibis_cloud_spanner/__init__.py
49
49
  third_party/ibis/ibis_cloud_spanner/api.py
50
50
  third_party/ibis/ibis_cloud_spanner/client.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.6.0"
21
+ version = "7.7.0"
22
22
  release_status = "Development Status :: 4 - Beta"
23
23
 
24
24
  with open("README.md", "r") as fh: