google-pso-data-validator 5.0.0__tar.gz → 5.1.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 (82) hide show
  1. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/PKG-INFO +14 -14
  2. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/README.md +7 -5
  3. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/cli_tools.py +25 -4
  4. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/clients.py +17 -0
  5. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/config_manager.py +7 -3
  6. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/consts.py +1 -0
  7. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/data_validation.py +3 -0
  8. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/gcs_helper.py +4 -4
  9. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/partition_builder.py +26 -26
  10. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/query_builder/query_builder.py +14 -2
  11. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/validation_builder.py +34 -4
  12. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/google_pso_data_validator.egg-info/PKG-INFO +14 -14
  13. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/google_pso_data_validator.egg-info/requires.txt +3 -4
  14. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/setup.py +7 -9
  15. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_impala/api.py +4 -0
  16. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/LICENSE +0 -0
  17. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/__init__.py +0 -0
  18. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/__main__.py +0 -0
  19. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/app.py +0 -0
  20. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/client_info.py +0 -0
  21. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/combiner.py +0 -0
  22. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/exceptions.py +0 -0
  23. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/jellyfish_distance.py +0 -0
  24. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/metadata.py +0 -0
  25. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/query_builder/__init__.py +0 -0
  26. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  27. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/query_builder/random_row_builder.py +0 -0
  28. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/result_handlers/__init__.py +0 -0
  29. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/result_handlers/bigquery.py +0 -0
  30. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/result_handlers/text.py +0 -0
  31. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/schema_validation.py +0 -0
  32. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/secret_manager.py +0 -0
  33. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/data_validation/state_manager.py +0 -0
  34. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
  35. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  36. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  37. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  38. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/setup.cfg +0 -0
  39. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  40. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_addon/api.py +0 -0
  41. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_addon/operations.py +0 -0
  42. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  43. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  44. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  45. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
  46. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  47. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  48. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  49. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  50. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  51. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
  52. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_db2/api.py +0 -0
  53. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  54. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
  55. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_db2/registry.py +0 -0
  56. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
  57. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
  58. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  59. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  60. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  61. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
  62. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  63. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  64. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
  65. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  66. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  67. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
  68. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  69. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  70. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  71. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  72. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
  73. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  74. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  75. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  76. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  77. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  78. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
  79. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  80. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  81. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  82. {google_pso_data_validator-5.0.0 → google_pso_data_validator-5.1.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-pso-data-validator
3
- Version: 5.0.0
3
+ Version: 5.1.0
4
4
  Summary: A package to enable easy data validation
5
- Home-page: https://github.com/pypa/sampleproject
6
- Author: Dylan Hercher
7
- Author-email: dhercher@google.com
8
- Classifier: Development Status :: 3 - Alpha
5
+ Author: PSO DVT Engineering team
6
+ Author-email: data-validator-eng@google.com
7
+ Classifier: Development Status :: 4 - Beta
9
8
  Classifier: License :: OSI Approved :: Apache Software License
10
9
  Classifier: Programming Language :: Python
11
10
  Classifier: Programming Language :: Python :: 3.8
@@ -16,7 +15,7 @@ Requires-Python: >=3.8
16
15
  Description-Content-Type: text/markdown
17
16
  License-File: LICENSE
18
17
  Requires-Dist: attrs==23.1.0
19
- Requires-Dist: grpcio==1.53.0
18
+ Requires-Dist: grpcio==1.53.2
20
19
  Requires-Dist: lazy-object-proxy==1.9.0
21
20
  Requires-Dist: marshmallow==3.19.0
22
21
  Requires-Dist: fsspec>=2022.8.2
@@ -24,7 +23,7 @@ Requires-Dist: google-api-python-client==2.91.0
24
23
  Requires-Dist: ibis-framework==5.1.0
25
24
  Requires-Dist: impyla==0.18.0
26
25
  Requires-Dist: SQLAlchemy==1.4.49
27
- Requires-Dist: PyMySQL==1.1.0
26
+ Requires-Dist: PyMySQL==1.1.1
28
27
  Requires-Dist: psycopg2-binary==2.9.6
29
28
  Requires-Dist: PyYAML==6.0
30
29
  Requires-Dist: pandas==1.5.3
@@ -35,12 +34,11 @@ Requires-Dist: google-cloud-bigquery==3.11.3
35
34
  Requires-Dist: google-cloud-bigquery-storage==2.20.0
36
35
  Requires-Dist: google-cloud-spanner==3.36.0
37
36
  Requires-Dist: google-cloud-storage==2.10.0
38
- Requires-Dist: setuptools>=34.0.0
39
37
  Requires-Dist: jellyfish==1.0.0
40
38
  Requires-Dist: tabulate==0.9.0
41
39
  Requires-Dist: Flask==2.3.2
42
40
  Requires-Dist: parsy==2.1
43
- Requires-Dist: google-cloud-secret-manager<=2.15.0
41
+ Requires-Dist: google-cloud-secret-manager>=2.16.0
44
42
  Provides-Extra: apache-airflow
45
43
  Requires-Dist: 1.10.11; extra == "apache-airflow"
46
44
  Provides-Extra: pyspark
@@ -256,6 +254,8 @@ data-validation (--verbose or -v) (--log-level or -ll) validate row
256
254
  Row batch size used for random row filters (default 10,000).
257
255
  [--filter-status or -fs STATUSES_LIST]
258
256
  Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned.
257
+ [--trim-string-pks, -tsp]
258
+ Trims string based primary key values, intended for use when one engine uses padded string semantics (e.g. CHAR(n)) and the other does not (e.g. VARCHAR(n)).
259
259
  ```
260
260
  #### Generate Table Partitions for Large Table Row Validations
261
261
 
@@ -357,12 +357,12 @@ data-validation (--verbose or -v) (--log-level or -ll) validate custom-query col
357
357
  Source sql query
358
358
  Either --source-query or --source-query-file must be provided
359
359
  --source-query-file SOURCE_QUERY_FILE, -sqf SOURCE_QUERY_FILE
360
- File containing the source sql commands
360
+ File containing the source sql command. Supports GCS and local paths.
361
361
  --target-query TARGET_QUERY, -tq TARGET_QUERY
362
362
  Target sql query
363
363
  Either --target-query or --target-query-file must be provided
364
364
  --target-query-file TARGET_QUERY_FILE, -tqf TARGET_QUERY_FILE
365
- File containing the target sql commands
365
+ File containing the target sql command. Supports GCS and local paths.
366
366
  [--count COLUMNS] Comma separated list of columns for count or * for all columns
367
367
  [--sum COLUMNS] Comma separated list of columns for sum or * for all numeric
368
368
  [--min COLUMNS] Comma separated list of columns for min or * for all numeric
@@ -419,12 +419,12 @@ data-validation (--verbose or -v) (--log-level or -ll) validate custom-query row
419
419
  Source sql query
420
420
  Either --source-query or --source-query-file must be provided
421
421
  --source-query-file SOURCE_QUERY_FILE, -sqf SOURCE_QUERY_FILE
422
- File containing the source sql commands
422
+ File containing the source sql command. Supports GCS and local paths.
423
423
  --target-query TARGET_QUERY, -tq TARGET_QUERY
424
424
  Target sql query
425
425
  Either --target-query or --target-query-file must be provided
426
426
  --target-query-file TARGET_QUERY_FILE, -tqf TARGET_QUERY_FILE
427
- File containing the target sql commands
427
+ File containing the target sql command. Supports GCS and local paths.
428
428
  --comparison-fields or -comp-fields FIELDS
429
429
  Comma separated list of columns to compare. Can either be a physical column or an alias
430
430
  See: *Calculated Fields* section for details
@@ -522,7 +522,7 @@ View the complete YAML file for a Grouped Column validation on the
522
522
  You can scale DVT for large table validations by running the tool in a distributed manner. To optimize the validation speed for large tables, you can use GKE Jobs ([Google Kubernetes Jobs](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-workloads-overview#batch_jobs)) or [Cloud Run Jobs](https://cloud.google.com/run/docs/create-jobs). If you are not familiar with Kubernetes or Cloud Run Jobs, see [Scaling DVT with Distributed Jobs](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/internal/distributed_jobs.md) for a detailed overview.
523
523
 
524
524
 
525
- We recommend first generating table partitions with the `generate-table-partitions` command for your large tables. Then, use Cloud Run or GKE to distribute validating each chunk in parallel. See the [Cloud Run Jobs Quickstart sample](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples/cloud_run_jobs) to get started.
525
+ We recommend first generating table partitions with the `generate-table-partitions` command for your large tables. Then, use Cloud Run or GKE to distribute validating each chunk in parallel. See the [Cloud Run Jobs Quickstart sample](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples/cloud_run_jobs) to get started.
526
526
 
527
527
  When running DVT in a distributed fashion, both the `--kube-completions` and `--config-dir` flags are required. The `--kube-completions` flag specifies that the validation is being run in indexed completion mode in Kubernetes or as multiple independent tasks in Cloud Run. If the `-kc` option is used and you are not running in indexed mode, you will receive a warning and the container will process all the validations sequentially. If the `-kc` option is used and a config directory is not provided (i.e. a `--config-file` is provided instead), a warning is issued.
528
528
 
@@ -208,6 +208,8 @@ data-validation (--verbose or -v) (--log-level or -ll) validate row
208
208
  Row batch size used for random row filters (default 10,000).
209
209
  [--filter-status or -fs STATUSES_LIST]
210
210
  Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned.
211
+ [--trim-string-pks, -tsp]
212
+ Trims string based primary key values, intended for use when one engine uses padded string semantics (e.g. CHAR(n)) and the other does not (e.g. VARCHAR(n)).
211
213
  ```
212
214
  #### Generate Table Partitions for Large Table Row Validations
213
215
 
@@ -309,12 +311,12 @@ data-validation (--verbose or -v) (--log-level or -ll) validate custom-query col
309
311
  Source sql query
310
312
  Either --source-query or --source-query-file must be provided
311
313
  --source-query-file SOURCE_QUERY_FILE, -sqf SOURCE_QUERY_FILE
312
- File containing the source sql commands
314
+ File containing the source sql command. Supports GCS and local paths.
313
315
  --target-query TARGET_QUERY, -tq TARGET_QUERY
314
316
  Target sql query
315
317
  Either --target-query or --target-query-file must be provided
316
318
  --target-query-file TARGET_QUERY_FILE, -tqf TARGET_QUERY_FILE
317
- File containing the target sql commands
319
+ File containing the target sql command. Supports GCS and local paths.
318
320
  [--count COLUMNS] Comma separated list of columns for count or * for all columns
319
321
  [--sum COLUMNS] Comma separated list of columns for sum or * for all numeric
320
322
  [--min COLUMNS] Comma separated list of columns for min or * for all numeric
@@ -371,12 +373,12 @@ data-validation (--verbose or -v) (--log-level or -ll) validate custom-query row
371
373
  Source sql query
372
374
  Either --source-query or --source-query-file must be provided
373
375
  --source-query-file SOURCE_QUERY_FILE, -sqf SOURCE_QUERY_FILE
374
- File containing the source sql commands
376
+ File containing the source sql command. Supports GCS and local paths.
375
377
  --target-query TARGET_QUERY, -tq TARGET_QUERY
376
378
  Target sql query
377
379
  Either --target-query or --target-query-file must be provided
378
380
  --target-query-file TARGET_QUERY_FILE, -tqf TARGET_QUERY_FILE
379
- File containing the target sql commands
381
+ File containing the target sql command. Supports GCS and local paths.
380
382
  --comparison-fields or -comp-fields FIELDS
381
383
  Comma separated list of columns to compare. Can either be a physical column or an alias
382
384
  See: *Calculated Fields* section for details
@@ -474,7 +476,7 @@ View the complete YAML file for a Grouped Column validation on the
474
476
  You can scale DVT for large table validations by running the tool in a distributed manner. To optimize the validation speed for large tables, you can use GKE Jobs ([Google Kubernetes Jobs](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-workloads-overview#batch_jobs)) or [Cloud Run Jobs](https://cloud.google.com/run/docs/create-jobs). If you are not familiar with Kubernetes or Cloud Run Jobs, see [Scaling DVT with Distributed Jobs](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/internal/distributed_jobs.md) for a detailed overview.
475
477
 
476
478
 
477
- We recommend first generating table partitions with the `generate-table-partitions` command for your large tables. Then, use Cloud Run or GKE to distribute validating each chunk in parallel. See the [Cloud Run Jobs Quickstart sample](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples/cloud_run_jobs) to get started.
479
+ We recommend first generating table partitions with the `generate-table-partitions` command for your large tables. Then, use Cloud Run or GKE to distribute validating each chunk in parallel. See the [Cloud Run Jobs Quickstart sample](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples/cloud_run_jobs) to get started.
478
480
 
479
481
  When running DVT in a distributed fashion, both the `--kube-completions` and `--config-dir` flags are required. The `--kube-completions` flag specifies that the validation is being run in indexed completion mode in Kubernetes or as multiple independent tasks in Cloud Run. If the `-kc` option is used and you are not running in indexed mode, you will receive a warning and the container will process all the validations sequentially. If the `-kc` option is used and a config directory is not provided (i.e. a `--config-file` is provided instead), a warning is issued.
480
482
 
@@ -484,6 +484,15 @@ def _configure_row_parser(row_parser):
484
484
  "-rbs",
485
485
  help="Row batch size used for random row filters (default 10,000).",
486
486
  )
487
+ optional_arguments.add_argument(
488
+ "--trim-string-pks",
489
+ "-tsp",
490
+ action="store_true",
491
+ help=(
492
+ "Trims string based primary key values, intended for use when one engine uses "
493
+ "padded string semantics (e.g. CHAR(n)) and the other does not (e.g. VARCHAR(n))."
494
+ ),
495
+ )
487
496
 
488
497
  # Group required arguments
489
498
  required_arguments = row_parser.add_argument_group("required arguments")
@@ -954,6 +963,18 @@ def _add_common_arguments(optional_arguments, required_arguments):
954
963
  )
955
964
 
956
965
 
966
+ def _check_no_partitions(value: str) -> int:
967
+ """Check that number of partitions is between [2-10,000]
968
+ Using function to validate rather than choices as error message prints all choices.
969
+ """
970
+ if value.isdigit() and 2 <= int(value) <= 10000:
971
+ return int(value)
972
+ else:
973
+ raise argparse.ArgumentTypeError(
974
+ f"{value} is not valid for number of partitions, use number in range 2 to 10000"
975
+ )
976
+
977
+
957
978
  def _add_common_partition_arguments(optional_arguments, required_arguments):
958
979
  """Add all arguments common to get-partition command"""
959
980
 
@@ -978,10 +999,9 @@ def _add_common_partition_arguments(optional_arguments, required_arguments):
978
999
  "--partition-num",
979
1000
  "-pn",
980
1001
  required=True,
981
- help="Number of partitions/config files to generate",
982
- type=int,
983
- choices=range(1, 1001),
984
- metavar="[1-1000]",
1002
+ help="Number of partitions/config files to generate, a number from 2 to 10,000",
1003
+ type=_check_no_partitions,
1004
+ metavar="[2-10000]",
985
1005
  )
986
1006
 
987
1007
  # Optional arguments
@@ -1387,6 +1407,7 @@ def get_pre_build_configs(args: Namespace, validate_cmd: str) -> List[Dict]:
1387
1407
  "result_handler_config": result_handler_config,
1388
1408
  "filter_config": filter_config,
1389
1409
  "filter_status": filter_status,
1410
+ "trim_string_pks": getattr(args, "trim_string_pks", False),
1390
1411
  "verbose": args.verbose,
1391
1412
  }
1392
1413
  pre_build_configs_list.append(pre_build_configs)
@@ -274,6 +274,23 @@ def get_max_column_length(client):
274
274
  return 128
275
275
 
276
276
 
277
+ def get_max_in_list_size(client, in_list_over_expressions=False):
278
+ if client.name == "snowflake":
279
+ if in_list_over_expressions:
280
+ # This is a workaround for Snowflake limitation:
281
+ # SQL compilation error: In-list contains more than 50 non-constant values
282
+ # getattr(..., "cast") expression above is looking for lists where the contents are casts and not simple literals.
283
+ return 50
284
+ else:
285
+ return 16000
286
+ elif is_oracle_client(client):
287
+ # This is a workaround for Oracle limitation:
288
+ # ORA-01795: maximum number of expressions in a list is 1000
289
+ return 1000
290
+ else:
291
+ return None
292
+
293
+
277
294
  CLIENT_LOOKUP = {
278
295
  "BigQuery": get_bigquery_client,
279
296
  "Impala": impala_connect,
@@ -112,6 +112,10 @@ class ConfigManager(object):
112
112
  """Return number of random rows or None."""
113
113
  return self.random_row_batch_size() if self.use_random_rows() else None
114
114
 
115
+ def trim_string_pks(self):
116
+ """Return if the validation should trim string primary keys."""
117
+ return self._config.get(consts.CONFIG_TRIM_STRING_PKS) or False
118
+
115
119
  def process_in_memory(self):
116
120
  """Return whether to process in memory or on a remote platform."""
117
121
  return True
@@ -459,6 +463,7 @@ class ConfigManager(object):
459
463
  result_handler_config=None,
460
464
  filter_config=None,
461
465
  filter_status=None,
466
+ trim_string_pks=None,
462
467
  verbose=False,
463
468
  ):
464
469
  if isinstance(filter_config, dict):
@@ -487,6 +492,7 @@ class ConfigManager(object):
487
492
  consts.CONFIG_USE_RANDOM_ROWS: use_random_rows,
488
493
  consts.CONFIG_RANDOM_ROW_BATCH_SIZE: random_row_batch_size,
489
494
  consts.CONFIG_FILTER_STATUS: filter_status,
495
+ consts.CONFIG_TRIM_STRING_PKS: trim_string_pks,
490
496
  }
491
497
 
492
498
  return ConfigManager(
@@ -1034,8 +1040,7 @@ class ConfigManager(object):
1034
1040
  """Return query from input file"""
1035
1041
  query = ""
1036
1042
  try:
1037
- file = open(filename, "r")
1038
- query = file.read()
1043
+ query = gcs_helper.read_file(filename, download_as_text=True)
1039
1044
  query = query.rstrip(";\n")
1040
1045
  except IOError:
1041
1046
  logging.error("Cannot read query file: ", filename)
@@ -1045,7 +1050,6 @@ class ConfigManager(object):
1045
1050
  "Expected file with sql query, got empty file or file with white spaces. "
1046
1051
  f"input file: {filename}"
1047
1052
  )
1048
- file.close()
1049
1053
  return query
1050
1054
 
1051
1055
  def get_query_from_inline(self, inline_query):
@@ -45,6 +45,7 @@ CONFIG_CALCULATED_TARGET_COLUMNS = "target_calculated_columns"
45
45
  CONFIG_USE_RANDOM_ROWS = "use_random_rows"
46
46
  CONFIG_RANDOM_ROW_BATCH_SIZE = "random_row_batch_size"
47
47
  CONFIG_PRIMARY_KEYS = "primary_keys"
48
+ CONFIG_TRIM_STRING_PKS = "trim_string_pks"
48
49
  CONFIG_SOURCE_COLUMN = "source_column"
49
50
  CONFIG_TARGET_COLUMN = "target_column"
50
51
  CONFIG_THRESHOLD = "threshold"
@@ -142,6 +142,9 @@ class DataValidation(object):
142
142
  **{source_pk_column: query[source_pk_column].cast("string")}
143
143
  )
144
144
 
145
+ if self.config_manager.trim_string_pks():
146
+ query = query.mutate(**{source_pk_column: query[source_pk_column].rstrip()})
147
+
145
148
  random_rows = self.config_manager.source_client.execute(query)
146
149
  if len(random_rows) == 0:
147
150
  return
@@ -50,12 +50,12 @@ def _get_gcs_file_path(gcs_file_path: str) -> str:
50
50
  return "".join(gcs_file_path[5:].split("/", 1)[1:])
51
51
 
52
52
 
53
- def _read_gcs_file(file_path: str) -> bytes:
53
+ def _read_gcs_file(file_path: str, download_as_text: bool = False):
54
54
  gcs_bucket = get_gcs_bucket(file_path)
55
55
  blob = gcs_bucket.blob(_get_gcs_file_path(file_path))
56
56
  if not blob:
57
57
  raise ValueError(f"Invalid Cloud Storage Path: {file_path}")
58
- return blob.download_as_bytes()
58
+ return blob.download_as_text() if download_as_text else blob.download_as_bytes()
59
59
 
60
60
 
61
61
  def _write_gcs_file(file_path: str, data: str):
@@ -64,9 +64,9 @@ def _write_gcs_file(file_path: str, data: str):
64
64
  blob.upload_from_string(data)
65
65
 
66
66
 
67
- def read_file(file_path: str):
67
+ def read_file(file_path: str, download_as_text: bool = False):
68
68
  if _is_gcs_path(file_path):
69
- return _read_gcs_file(file_path)
69
+ return _read_gcs_file(file_path, download_as_text)
70
70
  else:
71
71
  with open(file_path, "r") as f:
72
72
  return f.read()
@@ -32,7 +32,6 @@ class PartitionBuilder:
32
32
  self.table_count = len(config_managers)
33
33
  self.args = args
34
34
  self.config_dir = self._get_arg_config_dir()
35
- self.primary_keys = self._get_primary_keys()
36
35
 
37
36
  def _get_arg_config_dir(self) -> str:
38
37
  """Return String yaml config folder path."""
@@ -41,10 +40,6 @@ class PartitionBuilder:
41
40
 
42
41
  return self.args.config_dir
43
42
 
44
- def _get_primary_keys(self) -> str:
45
- """Return the Primary Keys"""
46
- return cli_tools.get_arg_list(self.args.primary_keys)
47
-
48
43
  def _get_yaml_from_config(self, config_manager: ConfigManager) -> Dict:
49
44
  """Return dict objects formatted for yaml validations.
50
45
 
@@ -106,8 +101,13 @@ class PartitionBuilder:
106
101
  for config_manager in self.config_managers: # For each pair of tables
107
102
  validation_builder = ValidationBuilder(config_manager)
108
103
 
104
+ source_pks, target_pks = [], []
105
+ for pk in config_manager.primary_keys:
106
+ source_pks.append(pk["source_column"])
107
+ target_pks.append(pk["target_column"])
108
+
109
109
  source_partition_row_builder = PartitionRowBuilder(
110
- self.primary_keys,
110
+ source_pks,
111
111
  config_manager.source_client,
112
112
  config_manager.source_schema,
113
113
  config_manager.source_table,
@@ -115,7 +115,7 @@ class PartitionBuilder:
115
115
  )
116
116
  source_table = source_partition_row_builder.query
117
117
  target_partition_row_builder = PartitionRowBuilder(
118
- self.primary_keys,
118
+ target_pks,
119
119
  config_manager.target_client,
120
120
  config_manager.target_schema,
121
121
  config_manager.target_table,
@@ -150,9 +150,9 @@ class PartitionBuilder:
150
150
  # First we number each row in the source table. Using row_number instead of ntile since it is
151
151
  # available on all platforms (Teradata does not support NTILE). For our purposes, it is likely
152
152
  # more efficient
153
- window1 = ibis.window(order_by=self.primary_keys)
153
+ window1 = ibis.window(order_by=source_pks)
154
154
  row_number = (ibis.row_number().over(window1) + 1).name(consts.DVT_POS_COL)
155
- dvt_keys = self.primary_keys.copy()
155
+ dvt_keys = source_pks.copy()
156
156
  dvt_keys.append(row_number)
157
157
  rownum_table = source_table.select(dvt_keys)
158
158
  # Rownum table is just the primary key columns in the source table along with
@@ -182,7 +182,7 @@ class PartitionBuilder:
182
182
  )
183
183
  > 0
184
184
  )
185
- first_keys_table = rownum_table[cond].order_by(self.primary_keys)
185
+ first_keys_table = rownum_table[cond].order_by(source_pks)
186
186
 
187
187
  # Up until this point, we have built the table expression, have not executed the query yet.
188
188
  # The query is now executed to find the first element of each partition
@@ -219,13 +219,13 @@ class PartitionBuilder:
219
219
 
220
220
  filter_source_clause = less_than_value(
221
221
  source_table,
222
- self.primary_keys,
223
- first_elements[1, : len(self.primary_keys)],
222
+ source_pks,
223
+ first_elements[1, : len(source_pks)],
224
224
  )
225
225
  filter_target_clause = less_than_value(
226
226
  target_table,
227
- self.primary_keys,
228
- first_elements[1, : len(self.primary_keys)],
227
+ target_pks,
228
+ first_elements[1, : len(target_pks)],
229
229
  )
230
230
  source_where_list.append(
231
231
  self._extract_where(
@@ -243,21 +243,21 @@ class PartitionBuilder:
243
243
  for i in range(1, first_elements.shape[0] - 1):
244
244
  filter_source_clause = geq_value(
245
245
  source_table,
246
- self.primary_keys,
247
- first_elements[i, : len(self.primary_keys)],
246
+ source_pks,
247
+ first_elements[i, : len(source_pks)],
248
248
  ) & less_than_value(
249
249
  source_table,
250
- self.primary_keys,
251
- first_elements[i + 1, : len(self.primary_keys)],
250
+ source_pks,
251
+ first_elements[i + 1, : len(source_pks)],
252
252
  )
253
253
  filter_target_clause = geq_value(
254
254
  target_table,
255
- self.primary_keys,
256
- first_elements[i, : len(self.primary_keys)],
255
+ target_pks,
256
+ first_elements[i, : len(target_pks)],
257
257
  ) & less_than_value(
258
258
  target_table,
259
- self.primary_keys,
260
- first_elements[i + 1, : len(self.primary_keys)],
259
+ target_pks,
260
+ first_elements[i + 1, : len(target_pks)],
261
261
  )
262
262
  source_where_list.append(
263
263
  self._extract_where(
@@ -273,13 +273,13 @@ class PartitionBuilder:
273
273
  )
274
274
  filter_source_clause = geq_value(
275
275
  source_table,
276
- self.primary_keys,
277
- first_elements[len(first_elements) - 1, : len(self.primary_keys)],
276
+ source_pks,
277
+ first_elements[len(first_elements) - 1, : len(source_pks)],
278
278
  )
279
279
  filter_target_clause = geq_value(
280
280
  target_table,
281
- self.primary_keys,
282
- first_elements[len(first_elements) - 1, : len(self.primary_keys)],
281
+ target_pks,
282
+ first_elements[len(first_elements) - 1, : len(target_pks)],
283
283
  )
284
284
  source_where_list.append(
285
285
  self._extract_where(
@@ -179,6 +179,10 @@ class FilterField(object):
179
179
  """
180
180
  return FilterField(None, left=expr)
181
181
 
182
+ @staticmethod
183
+ def or_(field_list: list):
184
+ return FilterField(ibis.or_, left=field_list)
185
+
182
186
  def compile(self, ibis_table):
183
187
  if self.expr is None:
184
188
  return operations.compile_raw_sql(ibis_table, self.left)
@@ -189,11 +193,16 @@ class FilterField(object):
189
193
  if self.right_field:
190
194
  self.right = ibis_table[self.right_field]
191
195
 
192
- return self.expr(self.left, self.right)
196
+ if self.expr == ibis.or_:
197
+ return self.expr(*[_.compile(ibis_table) for _ in self.left])
198
+ else:
199
+ return self.expr(self.left, self.right)
193
200
 
194
201
 
195
202
  class ComparisonField(object):
196
- def __init__(self, field_name, alias=None, cast=None):
203
+ def __init__(
204
+ self, field_name: str, alias: str = None, cast: str = None, trim: bool = None
205
+ ):
197
206
  """A representation of a comparison field used to build a query.
198
207
 
199
208
  Args:
@@ -204,6 +213,7 @@ class ComparisonField(object):
204
213
  self.field_name = field_name
205
214
  self.alias = alias
206
215
  self.cast = cast
216
+ self.trim = trim
207
217
 
208
218
  def compile(self, ibis_table):
209
219
  # Fields are supplied on compile or on build
@@ -211,6 +221,8 @@ class ComparisonField(object):
211
221
  alias = self.alias or self.field_name
212
222
  if self.cast:
213
223
  comparison_field = comparison_field.force_cast(self.cast)
224
+ elif self.trim and comparison_field.type().is_string():
225
+ comparison_field = comparison_field.rstrip()
214
226
  comparison_field = comparison_field.name(alias)
215
227
 
216
228
  return comparison_field
@@ -15,6 +15,7 @@ import logging
15
15
  from copy import deepcopy
16
16
 
17
17
  from data_validation import consts, metadata
18
+ from data_validation.clients import get_max_in_list_size
18
19
  from data_validation.query_builder.query_builder import (
19
20
  AggregateField,
20
21
  CalculatedField,
@@ -25,6 +26,11 @@ from data_validation.query_builder.query_builder import (
25
26
  )
26
27
 
27
28
 
29
+ def list_to_sublists(id_list: list, max_size: int) -> list:
30
+ """Return a list of items as a list of lists based on a max list length of max_size."""
31
+ return [id_list[_ : _ + max_size] for _ in range(0, len(id_list), max_size)]
32
+
33
+
28
34
  class ValidationBuilder(object):
29
35
  def __init__(self, config_manager):
30
36
  """Initialize a ValidationBuilder client which supplies the
@@ -60,6 +66,27 @@ class ValidationBuilder(object):
60
66
  self.add_primary_keys()
61
67
  self.add_query_limit()
62
68
 
69
+ def _construct_isin_filter(
70
+ self, client, column_name: str, in_list: list
71
+ ) -> FilterField:
72
+ """Return a FilterField object that is either isin(...) or (isin(...) OR isin(...) OR...)."""
73
+
74
+ max_in_list_size = get_max_in_list_size(
75
+ client,
76
+ in_list_over_expressions=bool(
77
+ in_list and getattr(in_list[0], "cast", None)
78
+ ),
79
+ )
80
+ if max_in_list_size and len(in_list) > max_in_list_size:
81
+ source_filters = [
82
+ FilterField.isin(column_name, _)
83
+ for _ in list_to_sublists(in_list, max_in_list_size)
84
+ ]
85
+ return FilterField.or_(source_filters)
86
+
87
+ else:
88
+ return FilterField.isin(column_name, in_list)
89
+
63
90
  def clone(self):
64
91
  cloned_builder = ValidationBuilder(self.config_manager)
65
92
 
@@ -237,12 +264,13 @@ class ValidationBuilder(object):
237
264
  # grab calc field metadata
238
265
  alias = primary_key.get(consts.CONFIG_FIELD_ALIAS)
239
266
  cast = primary_key.get(consts.CONFIG_CAST)
267
+ trim = self.config_manager.trim_string_pks()
240
268
  # check if valid calc field and return correct object
241
269
  source_field = ComparisonField(
242
- field_name=source_field_name, alias=alias, cast=cast
270
+ field_name=source_field_name, alias=alias, cast=cast, trim=trim
243
271
  )
244
272
  target_field = ComparisonField(
245
- field_name=target_field_name, alias=alias, cast=cast
273
+ field_name=target_field_name, alias=alias, cast=cast, trim=trim
246
274
  )
247
275
  self.source_builder.add_comparison_field(source_field)
248
276
  self.target_builder.add_comparison_field(target_field)
@@ -271,11 +299,13 @@ class ValidationBuilder(object):
271
299
  filter_field[consts.CONFIG_FILTER_TARGET_VALUE],
272
300
  )
273
301
  elif filter_field[consts.CONFIG_TYPE] == consts.FILTER_TYPE_ISIN:
274
- source_filter = FilterField.isin(
302
+ source_filter = self._construct_isin_filter(
303
+ self.source_client,
275
304
  filter_field[consts.CONFIG_FILTER_SOURCE_COLUMN],
276
305
  filter_field[consts.CONFIG_FILTER_SOURCE_VALUE],
277
306
  )
278
- target_filter = FilterField.isin(
307
+ target_filter = self._construct_isin_filter(
308
+ self.target_client,
279
309
  filter_field[consts.CONFIG_FILTER_TARGET_COLUMN],
280
310
  filter_field[consts.CONFIG_FILTER_TARGET_VALUE],
281
311
  )
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-pso-data-validator
3
- Version: 5.0.0
3
+ Version: 5.1.0
4
4
  Summary: A package to enable easy data validation
5
- Home-page: https://github.com/pypa/sampleproject
6
- Author: Dylan Hercher
7
- Author-email: dhercher@google.com
8
- Classifier: Development Status :: 3 - Alpha
5
+ Author: PSO DVT Engineering team
6
+ Author-email: data-validator-eng@google.com
7
+ Classifier: Development Status :: 4 - Beta
9
8
  Classifier: License :: OSI Approved :: Apache Software License
10
9
  Classifier: Programming Language :: Python
11
10
  Classifier: Programming Language :: Python :: 3.8
@@ -16,7 +15,7 @@ Requires-Python: >=3.8
16
15
  Description-Content-Type: text/markdown
17
16
  License-File: LICENSE
18
17
  Requires-Dist: attrs==23.1.0
19
- Requires-Dist: grpcio==1.53.0
18
+ Requires-Dist: grpcio==1.53.2
20
19
  Requires-Dist: lazy-object-proxy==1.9.0
21
20
  Requires-Dist: marshmallow==3.19.0
22
21
  Requires-Dist: fsspec>=2022.8.2
@@ -24,7 +23,7 @@ Requires-Dist: google-api-python-client==2.91.0
24
23
  Requires-Dist: ibis-framework==5.1.0
25
24
  Requires-Dist: impyla==0.18.0
26
25
  Requires-Dist: SQLAlchemy==1.4.49
27
- Requires-Dist: PyMySQL==1.1.0
26
+ Requires-Dist: PyMySQL==1.1.1
28
27
  Requires-Dist: psycopg2-binary==2.9.6
29
28
  Requires-Dist: PyYAML==6.0
30
29
  Requires-Dist: pandas==1.5.3
@@ -35,12 +34,11 @@ Requires-Dist: google-cloud-bigquery==3.11.3
35
34
  Requires-Dist: google-cloud-bigquery-storage==2.20.0
36
35
  Requires-Dist: google-cloud-spanner==3.36.0
37
36
  Requires-Dist: google-cloud-storage==2.10.0
38
- Requires-Dist: setuptools>=34.0.0
39
37
  Requires-Dist: jellyfish==1.0.0
40
38
  Requires-Dist: tabulate==0.9.0
41
39
  Requires-Dist: Flask==2.3.2
42
40
  Requires-Dist: parsy==2.1
43
- Requires-Dist: google-cloud-secret-manager<=2.15.0
41
+ Requires-Dist: google-cloud-secret-manager>=2.16.0
44
42
  Provides-Extra: apache-airflow
45
43
  Requires-Dist: 1.10.11; extra == "apache-airflow"
46
44
  Provides-Extra: pyspark
@@ -256,6 +254,8 @@ data-validation (--verbose or -v) (--log-level or -ll) validate row
256
254
  Row batch size used for random row filters (default 10,000).
257
255
  [--filter-status or -fs STATUSES_LIST]
258
256
  Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned.
257
+ [--trim-string-pks, -tsp]
258
+ Trims string based primary key values, intended for use when one engine uses padded string semantics (e.g. CHAR(n)) and the other does not (e.g. VARCHAR(n)).
259
259
  ```
260
260
  #### Generate Table Partitions for Large Table Row Validations
261
261
 
@@ -357,12 +357,12 @@ data-validation (--verbose or -v) (--log-level or -ll) validate custom-query col
357
357
  Source sql query
358
358
  Either --source-query or --source-query-file must be provided
359
359
  --source-query-file SOURCE_QUERY_FILE, -sqf SOURCE_QUERY_FILE
360
- File containing the source sql commands
360
+ File containing the source sql command. Supports GCS and local paths.
361
361
  --target-query TARGET_QUERY, -tq TARGET_QUERY
362
362
  Target sql query
363
363
  Either --target-query or --target-query-file must be provided
364
364
  --target-query-file TARGET_QUERY_FILE, -tqf TARGET_QUERY_FILE
365
- File containing the target sql commands
365
+ File containing the target sql command. Supports GCS and local paths.
366
366
  [--count COLUMNS] Comma separated list of columns for count or * for all columns
367
367
  [--sum COLUMNS] Comma separated list of columns for sum or * for all numeric
368
368
  [--min COLUMNS] Comma separated list of columns for min or * for all numeric
@@ -419,12 +419,12 @@ data-validation (--verbose or -v) (--log-level or -ll) validate custom-query row
419
419
  Source sql query
420
420
  Either --source-query or --source-query-file must be provided
421
421
  --source-query-file SOURCE_QUERY_FILE, -sqf SOURCE_QUERY_FILE
422
- File containing the source sql commands
422
+ File containing the source sql command. Supports GCS and local paths.
423
423
  --target-query TARGET_QUERY, -tq TARGET_QUERY
424
424
  Target sql query
425
425
  Either --target-query or --target-query-file must be provided
426
426
  --target-query-file TARGET_QUERY_FILE, -tqf TARGET_QUERY_FILE
427
- File containing the target sql commands
427
+ File containing the target sql command. Supports GCS and local paths.
428
428
  --comparison-fields or -comp-fields FIELDS
429
429
  Comma separated list of columns to compare. Can either be a physical column or an alias
430
430
  See: *Calculated Fields* section for details
@@ -522,7 +522,7 @@ View the complete YAML file for a Grouped Column validation on the
522
522
  You can scale DVT for large table validations by running the tool in a distributed manner. To optimize the validation speed for large tables, you can use GKE Jobs ([Google Kubernetes Jobs](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-workloads-overview#batch_jobs)) or [Cloud Run Jobs](https://cloud.google.com/run/docs/create-jobs). If you are not familiar with Kubernetes or Cloud Run Jobs, see [Scaling DVT with Distributed Jobs](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/internal/distributed_jobs.md) for a detailed overview.
523
523
 
524
524
 
525
- We recommend first generating table partitions with the `generate-table-partitions` command for your large tables. Then, use Cloud Run or GKE to distribute validating each chunk in parallel. See the [Cloud Run Jobs Quickstart sample](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples/cloud_run_jobs) to get started.
525
+ We recommend first generating table partitions with the `generate-table-partitions` command for your large tables. Then, use Cloud Run or GKE to distribute validating each chunk in parallel. See the [Cloud Run Jobs Quickstart sample](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples/cloud_run_jobs) to get started.
526
526
 
527
527
  When running DVT in a distributed fashion, both the `--kube-completions` and `--config-dir` flags are required. The `--kube-completions` flag specifies that the validation is being run in indexed completion mode in Kubernetes or as multiple independent tasks in Cloud Run. If the `-kc` option is used and you are not running in indexed mode, you will receive a warning and the container will process all the validations sequentially. If the `-kc` option is used and a config directory is not provided (i.e. a `--config-file` is provided instead), a warning is issued.
528
528
 
@@ -1,5 +1,5 @@
1
1
  attrs==23.1.0
2
- grpcio==1.53.0
2
+ grpcio==1.53.2
3
3
  lazy-object-proxy==1.9.0
4
4
  marshmallow==3.19.0
5
5
  fsspec>=2022.8.2
@@ -7,7 +7,7 @@ google-api-python-client==2.91.0
7
7
  ibis-framework==5.1.0
8
8
  impyla==0.18.0
9
9
  SQLAlchemy==1.4.49
10
- PyMySQL==1.1.0
10
+ PyMySQL==1.1.1
11
11
  psycopg2-binary==2.9.6
12
12
  PyYAML==6.0
13
13
  pandas==1.5.3
@@ -18,12 +18,11 @@ google-cloud-bigquery==3.11.3
18
18
  google-cloud-bigquery-storage==2.20.0
19
19
  google-cloud-spanner==3.36.0
20
20
  google-cloud-storage==2.10.0
21
- setuptools>=34.0.0
22
21
  jellyfish==1.0.0
23
22
  tabulate==0.9.0
24
23
  Flask==2.3.2
25
24
  parsy==2.1
26
- google-cloud-secret-manager<=2.15.0
25
+ google-cloud-secret-manager>=2.16.0
27
26
 
28
27
  [apache-airflow]
29
28
  1.10.11
@@ -18,8 +18,8 @@ import setuptools
18
18
 
19
19
  name = "google-pso-data-validator"
20
20
  description = "A package to enable easy data validation"
21
- version = "5.0.0"
22
- release_status = "Development Status :: 3 - Alpha"
21
+ version = "5.1.0"
22
+ release_status = "Development Status :: 4 - Beta"
23
23
 
24
24
  with open("README.md", "r") as fh:
25
25
  long_description = fh.read()
@@ -27,7 +27,7 @@ with open("README.md", "r") as fh:
27
27
  dependencies = [
28
28
  # Dependency corrections from our requirements
29
29
  "attrs==23.1.0",
30
- "grpcio==1.53.0",
30
+ "grpcio==1.53.2",
31
31
  "lazy-object-proxy==1.9.0",
32
32
  "marshmallow==3.19.0",
33
33
  # Core dependencies
@@ -36,7 +36,7 @@ dependencies = [
36
36
  "ibis-framework==5.1.0",
37
37
  "impyla==0.18.0",
38
38
  "SQLAlchemy==1.4.49",
39
- "PyMySQL==1.1.0",
39
+ "PyMySQL==1.1.1",
40
40
  "psycopg2-binary==2.9.6",
41
41
  "PyYAML==6.0",
42
42
  "pandas==1.5.3",
@@ -47,12 +47,11 @@ dependencies = [
47
47
  "google-cloud-bigquery-storage==2.20.0",
48
48
  "google-cloud-spanner==3.36.0",
49
49
  "google-cloud-storage==2.10.0",
50
- "setuptools>=34.0.0",
51
50
  "jellyfish==1.0.0",
52
51
  "tabulate==0.9.0",
53
52
  "Flask==2.3.2",
54
53
  "parsy==2.1",
55
- "google-cloud-secret-manager<=2.15.0",
54
+ "google-cloud-secret-manager>=2.16.0",
56
55
  ]
57
56
 
58
57
  extras_require = {
@@ -74,11 +73,10 @@ setuptools.setup(
74
73
  name=name,
75
74
  description=description,
76
75
  version=version,
77
- author="Dylan Hercher",
78
- author_email="dhercher@google.com",
76
+ author="PSO DVT Engineering team",
77
+ author_email="data-validator-eng@google.com",
79
78
  long_description=long_description,
80
79
  long_description_content_type="text/markdown",
81
- url="https://github.com/pypa/sampleproject",
82
80
  packages=packages,
83
81
  classifiers=[
84
82
  release_status,
@@ -197,6 +197,10 @@ def _get_schema_using_query(self, query):
197
197
  # Removing LIMIT 0 around query since it returns no results in Hive
198
198
  cur = self.raw_sql(f"SELECT * FROM ({query}) t0 LIMIT 1")
199
199
  cur.fetchall()
200
+ cur.description = [
201
+ (description[0].replace("t0.", "", 1), *description[1:])
202
+ for description in cur.description
203
+ ]
200
204
  ibis_fields = self._adapt_types(cur.description)
201
205
  cur.release()
202
206