google-pso-data-validator 7.6.1__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.1/google_pso_data_validator.egg-info → google_pso_data_validator-7.7.0}/PKG-INFO +10 -3
  2. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/README.md +9 -2
  3. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/cli_tools.py +9 -2
  4. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/clients.py +27 -7
  5. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/consts.py +1 -0
  6. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/bigquery.py +1 -1
  7. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0/google_pso_data_validator.egg-info}/PKG-INFO +10 -3
  8. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/SOURCES.txt +2 -2
  9. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/setup.py +1 -1
  10. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_addon/operations.py +25 -62
  11. google_pso_data_validator-7.7.0/third_party/ibis/ibis_bigquery/__init__.py +142 -0
  12. google_pso_data_validator-7.7.0/third_party/ibis/ibis_bigquery/api.py +35 -0
  13. google_pso_data_validator-7.7.0/third_party/ibis/ibis_postgres/registry.py +86 -0
  14. google_pso_data_validator-7.6.1/third_party/ibis/ibis_biquery/api.py +0 -15
  15. google_pso_data_validator-7.6.1/third_party/ibis/ibis_postgres/registry.py +0 -43
  16. google_pso_data_validator-7.6.1/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  17. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/LICENSE +0 -0
  18. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/__init__.py +0 -0
  19. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/__main__.py +0 -0
  20. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/app.py +0 -0
  21. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/client_info.py +0 -0
  22. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/combiner.py +0 -0
  23. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/config_manager.py +0 -0
  24. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/data_validation.py +0 -0
  25. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/exceptions.py +0 -0
  26. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/find_tables.py +0 -0
  27. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/gcs_helper.py +0 -0
  28. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/jellyfish_distance.py +0 -0
  29. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/metadata.py +0 -0
  30. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/partition_builder.py +0 -0
  31. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/query_builder/__init__.py +0 -0
  32. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  33. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/query_builder/query_builder.py +0 -0
  34. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/query_builder/random_row_builder.py +0 -0
  35. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/raw_query.py +0 -0
  36. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/__init__.py +0 -0
  37. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/base_backend.py +0 -0
  38. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/factory.py +0 -0
  39. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/postgres.py +0 -0
  40. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/result_handlers/text.py +0 -0
  41. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/schema_validation.py +0 -0
  42. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/secret_manager.py +0 -0
  43. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/state_manager.py +0 -0
  44. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/util.py +0 -0
  45. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/data_validation/validation_builder.py +0 -0
  46. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  47. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  48. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/requires.txt +0 -0
  49. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  50. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/setup.cfg +0 -0
  51. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  52. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_addon/api.py +0 -0
  53. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  54. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  55. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  56. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
  57. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  58. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  59. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  60. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  61. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  62. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
  63. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/api.py +0 -0
  64. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  65. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
  66. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_db2/registry.py +0 -0
  67. {google_pso_data_validator-7.6.1/third_party/ibis/ibis_biquery → google_pso_data_validator-7.7.0/third_party/ibis/ibis_impala}/__init__.py +0 -0
  68. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_impala/api.py +0 -0
  69. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
  70. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  71. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  72. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
  73. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  74. {google_pso_data_validator-7.6.1/third_party/ibis/ibis_impala → google_pso_data_validator-7.7.0/third_party/ibis/ibis_mysql/base_sql_compiler}/__init__.py +0 -0
  75. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  76. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  77. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
  78. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  79. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  80. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
  81. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  82. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  83. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  84. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  85. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
  86. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  87. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  88. {google_pso_data_validator-7.6.1/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.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  90. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  91. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
  92. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  93. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  94. {google_pso_data_validator-7.6.1 → google_pso_data_validator-7.7.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  95. {google_pso_data_validator-7.6.1 → 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.1
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
@@ -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"
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-pso-data-validator
3
- Version: 7.6.1
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.1"
21
+ version = "7.7.0"
22
22
  release_status = "Development Status :: 4 - Beta"
23
23
 
24
24
  with open("README.md", "r") as fh:
@@ -61,7 +61,7 @@ from ibis.backends.postgres.compiler import PostgreSQLExprTranslator
61
61
  from ibis.expr.types import BinaryValue, NumericValue, StringValue, TemporalValue
62
62
 
63
63
  # Do not remove these lines, they trigger patching of Ibis code.
64
- import third_party.ibis.ibis_biquery.api # noqa
64
+ import third_party.ibis.ibis_bigquery.api # noqa
65
65
  import third_party.ibis.ibis_mysql.compiler # noqa
66
66
  from third_party.ibis.ibis_mssql import registry as mssql_registry
67
67
  from third_party.ibis.ibis_postgres import registry as postgres_registry
@@ -329,40 +329,6 @@ def sa_format_binary_length_oracle(translator, op):
329
329
  return sa.func.dbms_lob.getlength(arg)
330
330
 
331
331
 
332
- def sa_cast_decimal_when_scale_padded_fmt_fm(t, op):
333
- """Caters for engines that fully pad scale with 0s when casting decimal to string and support FM format."""
334
- # Add cast from numeric to string
335
- arg = op.arg
336
- typ = op.to
337
- arg_dtype = arg.output_dtype
338
-
339
- # Specialize going from numeric(p,s>0) to string
340
- if (
341
- arg_dtype.is_decimal()
342
- and arg_dtype.scale
343
- and arg_dtype.scale > 0
344
- and typ.is_string()
345
- ):
346
- sa_arg = t.translate(arg)
347
- # When casting a number to string PostgreSQL and Snowflake include the full scale, e.g.:
348
- # SELECT CAST(CAST(100 AS DECIMAL(5,2)) AS VARCHAR(10));
349
- # 100.00
350
- # This doesn't match most engines which would return "100".
351
- # Using to_char() function instead of cast to return a more typical value.
352
- # We've wrapped to_char in rtrim(".") due to whole numbers having a trailing ".".
353
- # Would have liked to use trim_scale but this is only available in PostgreSQL 13+
354
- # return (sa.cast(sa.func.trim_scale(arg), typ))
355
- precision = arg_dtype.precision or 38
356
- fmt = (
357
- "FM"
358
- + ("9" * (precision - arg_dtype.scale - 1))
359
- + "0."
360
- + ("9" * arg_dtype.scale)
361
- )
362
- return sa.func.rtrim(sa.func.to_char(sa_arg, fmt), ".")
363
- return None
364
-
365
-
366
332
  def sa_cast_hive(t, op):
367
333
  arg = op.arg
368
334
  typ = op.to
@@ -387,27 +353,6 @@ def sa_cast_hive(t, op):
387
353
  return cast_expr
388
354
 
389
355
 
390
- def sa_cast_postgres(t, op):
391
- custom_cast = sa_cast_decimal_when_scale_padded_fmt_fm(t, op)
392
- if custom_cast is not None:
393
- return custom_cast
394
-
395
- arg = op.arg
396
- typ = op.to
397
- arg_dtype = arg.output_dtype
398
-
399
- sa_arg = t.translate(arg)
400
- if arg_dtype.is_binary() and typ.is_string():
401
- # Binary to string cast is a "to hex" conversion for DVT.
402
- return sa.func.encode(sa_arg, sa.literal("hex"))
403
- elif arg_dtype.is_string() and typ.is_binary():
404
- # Binary from string cast is a "from hex" conversion for DVT.
405
- return sa.func.decode(sa_arg, sa.literal("hex"))
406
-
407
- # Follow the original Ibis code path.
408
- return sa_fixed_cast(t, op)
409
-
410
-
411
356
  def sa_cast_mssql(t, op):
412
357
  arg = op.arg
413
358
  typ = op.to
@@ -477,15 +422,33 @@ def sa_cast_mysql(t, op):
477
422
 
478
423
 
479
424
  def sa_cast_snowflake(t, op):
480
- custom_cast = sa_cast_decimal_when_scale_padded_fmt_fm(t, op)
481
- if custom_cast is not None:
482
- return custom_cast
483
-
484
425
  arg = op.arg
485
426
  typ = op.to
486
427
  arg_dtype = arg.output_dtype
487
-
488
428
  sa_arg = t.translate(arg)
429
+
430
+ # Specialize going from numeric(p,s>0) to string
431
+ if (
432
+ arg_dtype.is_decimal()
433
+ and arg_dtype.scale
434
+ and arg_dtype.scale > 0
435
+ and typ.is_string()
436
+ ):
437
+ # When casting a number to string Snowflake includes the full scale, e.g.:
438
+ # SELECT CAST(CAST(100 AS DECIMAL(5,2)) AS VARCHAR(10));
439
+ # 100.00
440
+ # This doesn't match most engines which would return "100".
441
+ # Using to_char() function instead of cast to return a more typical value.
442
+ # We've wrapped to_char in rtrim(".") due to whole numbers having a trailing ".".
443
+ precision = arg_dtype.precision or 38
444
+ fmt = (
445
+ "FM"
446
+ + ("9" * (precision - arg_dtype.scale - 1))
447
+ + "0."
448
+ + ("9" * arg_dtype.scale)
449
+ )
450
+ return sa.func.rtrim(sa.func.to_char(sa_arg, fmt), ".")
451
+
489
452
  if arg_dtype.is_binary() and typ.is_string():
490
453
  # Binary to string cast is a "to hex" conversion for DVT.
491
454
  return sa.func.hex_encode(sa_arg, sa.literal(0))
@@ -648,7 +611,7 @@ PostgreSQLExprTranslator._registry[
648
611
  ] = postgres_registry.sa_format_hashbytes
649
612
  PostgreSQLExprTranslator._registry[RawSQL] = sa_format_raw_sql
650
613
  PostgreSQLExprTranslator._registry[ToChar] = sa_format_to_char
651
- PostgreSQLExprTranslator._registry[ops.Cast] = sa_cast_postgres
614
+ PostgreSQLExprTranslator._registry[ops.Cast] = postgres_registry.sa_cast_postgres
652
615
  PostgreSQLExprTranslator._registry[BinaryLength] = sa_format_binary_length
653
616
  PostgreSQLExprTranslator._registry[
654
617
  ops.ExtractEpochSeconds
@@ -0,0 +1,142 @@
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
+ from typing import TYPE_CHECKING
16
+
17
+ import google.auth.credentials
18
+ import google.cloud.bigquery as bq
19
+ import pydata_google_auth
20
+ from pydata_google_auth import cache
21
+
22
+ from ibis.backends.bigquery import (
23
+ Backend as BigQueryBackend,
24
+ _create_client_info,
25
+ parse_project_and_dataset,
26
+ CLIENT_ID,
27
+ CLIENT_SECRET,
28
+ EXTERNAL_DATA_SCOPES,
29
+ SCOPES,
30
+ )
31
+
32
+ if TYPE_CHECKING:
33
+ import google.cloud.bigquery_storage_v1
34
+
35
+
36
+ class Backend(BigQueryBackend):
37
+ def __init__(self):
38
+ super().__init__()
39
+ self.storage_client = None
40
+
41
+ def do_connect(
42
+ self,
43
+ project_id: str = None,
44
+ dataset_id: str = "",
45
+ credentials: google.auth.credentials.Credentials = None,
46
+ application_name: str = None,
47
+ auth_local_webserver: bool = True,
48
+ auth_external_data: bool = False,
49
+ auth_cache: str = "default",
50
+ partition_column: str = "PARTITIONTIME",
51
+ # Custom DVT arguments:
52
+ bigquery_client: bq.Client = None,
53
+ bqstorage_client: "google.cloud.bigquery_storage_v1.BigQueryReadClient" = None,
54
+ ):
55
+ """Copy of Ibis v5 BigQuery do_connect() customized for DVT, see original method for docs."""
56
+ default_project_id = ""
57
+
58
+ if credentials is None:
59
+ scopes = SCOPES
60
+ if auth_external_data:
61
+ scopes = EXTERNAL_DATA_SCOPES
62
+
63
+ if auth_cache == "default":
64
+ credentials_cache = cache.ReadWriteCredentialsCache(
65
+ filename="ibis.json"
66
+ )
67
+ elif auth_cache == "reauth":
68
+ credentials_cache = cache.WriteOnlyCredentialsCache(
69
+ filename="ibis.json"
70
+ )
71
+ elif auth_cache == "none":
72
+ credentials_cache = cache.NOOP
73
+ else:
74
+ raise ValueError(
75
+ f"Got unexpected value for auth_cache = '{auth_cache}'. "
76
+ "Expected one of 'default', 'reauth', or 'none'."
77
+ )
78
+
79
+ credentials, default_project_id = pydata_google_auth.default(
80
+ scopes,
81
+ client_id=CLIENT_ID,
82
+ client_secret=CLIENT_SECRET,
83
+ credentials_cache=credentials_cache,
84
+ use_local_webserver=auth_local_webserver,
85
+ )
86
+
87
+ project_id = project_id or default_project_id
88
+
89
+ (
90
+ self.data_project,
91
+ self.billing_project,
92
+ self.dataset,
93
+ ) = parse_project_and_dataset(project_id, dataset_id)
94
+
95
+ if bigquery_client is None:
96
+ self.client = bq.Client(
97
+ project=self.billing_project,
98
+ credentials=credentials,
99
+ client_info=_create_client_info(application_name),
100
+ )
101
+ else:
102
+ self.client = bigquery_client
103
+ self.partition_column = partition_column
104
+ self.storage_client = bqstorage_client
105
+
106
+ def _cursor_to_arrow(
107
+ self,
108
+ cursor,
109
+ *,
110
+ method=None,
111
+ chunk_size: int = None,
112
+ ):
113
+ """Copy of Ibis v5 BigQuery _cursor_to_arrow() except can use custom DVT storage client"""
114
+ if method is None:
115
+
116
+ def method(result, storage_client=self.storage_client):
117
+ return result.to_arrow(
118
+ progress_bar_type=None,
119
+ # Include DVT specific storage client.
120
+ bqstorage_client=storage_client,
121
+ create_bqstorage_client=bool(not self.storage_client),
122
+ )
123
+
124
+ query = cursor.query
125
+ query_result = query.result(page_size=chunk_size)
126
+ # workaround potentially not having the ability to create read sessions
127
+ # in the dataset project
128
+ orig_project = query_result._project
129
+ query_result._project = self.billing_project
130
+ try:
131
+ arrow_obj = method(query_result)
132
+ finally:
133
+ query_result._project = orig_project
134
+ return arrow_obj
135
+
136
+ def list_primary_key_columns(self, database: str, table: str) -> list:
137
+ """Return a list of primary key column names."""
138
+ # TODO: Related to issue-1253, it's not clear if this is possible, we should revisit if it becomes a requirement.
139
+ return None
140
+
141
+ def dvt_list_tables(self, like=None, database=None):
142
+ return self.list_tables(like=like, database=database)
@@ -0,0 +1,35 @@
1
+ # Copyright 2024 Google Inc.
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
+
16
+ from third_party.ibis.ibis_bigquery import Backend as BigQueryBackend
17
+
18
+
19
+ def bigquery_connect(
20
+ project_id: str = None,
21
+ dataset_id: str = "",
22
+ credentials=None,
23
+ bigquery_client=None,
24
+ bqstorage_client=None,
25
+ ):
26
+ """Create a BigQuery Backend for use with Ibis."""
27
+ backend = BigQueryBackend()
28
+ backend.do_connect(
29
+ project_id=project_id,
30
+ dataset_id=dataset_id,
31
+ credentials=credentials,
32
+ bigquery_client=bigquery_client,
33
+ bqstorage_client=bqstorage_client,
34
+ )
35
+ return backend
@@ -0,0 +1,86 @@
1
+ # Copyright 2024 Google Inc.
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 sqlalchemy as sa
16
+ from ibis.backends.base.sql.alchemy.registry import _cast as sa_fixed_cast
17
+
18
+
19
+ def sa_format_hashbytes(translator, op):
20
+ arg = translator.translate(op.arg)
21
+ convert = sa.func.convert_to(arg, sa.sql.literal_column("'UTF8'"))
22
+ hash_func = sa.func.sha256(convert)
23
+ return sa.func.encode(hash_func, sa.sql.literal_column("'hex'"))
24
+
25
+
26
+ def sa_epoch_seconds(translator, op):
27
+ """Override for standard ExtractEpochSeconds but catering for larger second values.
28
+
29
+ This expression also truncates fractional seconds from the incoming datetime.
30
+ This matches behaviour on other SQL engines' epoch seconds expressions."""
31
+ arg = translator.translate(op.arg)
32
+ return sa.cast(
33
+ sa.extract("epoch", sa.func.date_trunc(sa.sql.literal_column("'second'"), arg)),
34
+ sa.BIGINT,
35
+ )
36
+
37
+
38
+ def sa_cast_postgres(t, op):
39
+ arg = op.arg
40
+ typ = op.to
41
+ arg_dtype = arg.output_dtype
42
+ sa_arg = t.translate(arg)
43
+
44
+ if arg_dtype.is_decimal() and typ.is_string():
45
+ if arg_dtype.scale is None:
46
+ # trim_scale() is only available in PostgreSQL 13+ but solves a lot of problems
47
+ # when trying to get consistently formatted numerics.
48
+ # We've documented a workaround for PostgreSQL 12 and prior versions.
49
+ return sa.cast(sa.func.trim_scale(sa_arg), t.get_sqla_type(typ))
50
+ elif arg_dtype.scale > 0:
51
+ # When casting a number to string PostgreSQL includes the full scale, e.g.:
52
+ # SELECT CAST(CAST(100 AS DECIMAL(5,2)) AS VARCHAR(10));
53
+ # 100.00
54
+ # This doesn't match most engines which would return "100".
55
+ # Using to_char() function instead of cast to return a more typical value.
56
+ # We've wrapped to_char in rtrim(".") due to whole numbers having a trailing ".".
57
+ #
58
+ # Potentially we could use trim_scale() here like when scale is None but this is
59
+ # a much more common scenario than decimal(scale=None) so I'm trying to minimize
60
+ # risk of needing a UDF.
61
+ precision = arg_dtype.precision or 38
62
+ fmt = (
63
+ "FM"
64
+ + ("9" * (precision - arg_dtype.scale - 1))
65
+ + "0."
66
+ + ("9" * arg_dtype.scale)
67
+ )
68
+ return sa.func.rtrim(sa.func.to_char(sa_arg, fmt), ".")
69
+ elif arg_dtype.is_binary() and typ.is_string():
70
+ # Binary to string cast is a "to hex" conversion for DVT.
71
+ return sa.func.encode(sa_arg, sa.literal("hex"))
72
+ elif arg_dtype.is_string() and typ.is_binary():
73
+ # Binary from string cast is a "from hex" conversion for DVT.
74
+ return sa.func.decode(sa_arg, sa.literal("hex"))
75
+
76
+ # Follow the original Ibis code path.
77
+ return sa_fixed_cast(t, op)
78
+
79
+
80
+ def sa_format_postgres_padded_char_length(translator, op):
81
+ """Inject concat of empty string into length(bpchar) to protect trailing spaces.
82
+
83
+ Without this workaround the bpchar value is implicitly cast to varchar and loses trailing spaces.
84
+ """
85
+ arg = translator.translate(op.arg)
86
+ return sa.func.char_length(sa.func.concat(arg, sa.text("''")))
@@ -1,15 +0,0 @@
1
- from ibis.backends.bigquery import Backend as BigQueryBackend
2
-
3
-
4
- def _list_primary_key_columns(self, database: str, table: str) -> list:
5
- """Return a list of primary key column names."""
6
- # TODO: Related to issue-1253, it's not clear if this is possible, we should revisit if it becomes a requirement.
7
- return None
8
-
9
-
10
- def _dvt_list_tables(self, like=None, database=None):
11
- return self.list_tables(like=like, database=database)
12
-
13
-
14
- BigQueryBackend.list_primary_key_columns = _list_primary_key_columns
15
- BigQueryBackend.dvt_list_tables = _dvt_list_tables
@@ -1,43 +0,0 @@
1
- # Copyright 2024 Google Inc.
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 sqlalchemy as sa
16
-
17
-
18
- def sa_format_hashbytes(translator, op):
19
- arg = translator.translate(op.arg)
20
- convert = sa.func.convert_to(arg, sa.sql.literal_column("'UTF8'"))
21
- hash_func = sa.func.sha256(convert)
22
- return sa.func.encode(hash_func, sa.sql.literal_column("'hex'"))
23
-
24
-
25
- def sa_epoch_seconds(translator, op):
26
- """Override for standard ExtractEpochSeconds but catering for larger second values.
27
-
28
- This expression also truncates fractional seconds from the incoming datetime.
29
- This matches behaviour on other SQL engines' epoch seconds expressions."""
30
- arg = translator.translate(op.arg)
31
- return sa.cast(
32
- sa.extract("epoch", sa.func.date_trunc(sa.sql.literal_column("'second'"), arg)),
33
- sa.BIGINT,
34
- )
35
-
36
-
37
- def sa_format_postgres_padded_char_length(translator, op):
38
- """Inject concat of empty string into length(bpchar) to protect trailing spaces.
39
-
40
- Without this workaround the bpchar value is implicitly cast to varchar and loses trailing spaces.
41
- """
42
- arg = translator.translate(op.arg)
43
- return sa.func.char_length(sa.func.concat(arg, sa.text("''")))