google-pso-data-validator 6.3.0__tar.gz → 6.4.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 (85) hide show
  1. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/PKG-INFO +1 -1
  2. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/__main__.py +37 -31
  3. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/cli_tools.py +26 -13
  4. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/config_manager.py +8 -1
  5. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/consts.py +7 -0
  6. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/data_validation.py +21 -7
  7. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/exceptions.py +5 -1
  8. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/metadata.py +1 -3
  9. google_pso_data_validator-6.4.0/data_validation/util.py +24 -0
  10. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/google_pso_data_validator.egg-info/PKG-INFO +1 -1
  11. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/google_pso_data_validator.egg-info/SOURCES.txt +1 -0
  12. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/setup.py +1 -1
  13. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_oracle/__init__.py +10 -0
  14. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/LICENSE +0 -0
  15. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/README.md +0 -0
  16. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/__init__.py +0 -0
  17. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/app.py +0 -0
  18. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/client_info.py +0 -0
  19. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/clients.py +0 -0
  20. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/combiner.py +0 -0
  21. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/find_tables.py +0 -0
  22. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/gcs_helper.py +0 -0
  23. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/jellyfish_distance.py +0 -0
  24. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/partition_builder.py +0 -0
  25. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/query_builder/__init__.py +0 -0
  26. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  27. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/query_builder/query_builder.py +0 -0
  28. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/query_builder/random_row_builder.py +0 -0
  29. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/result_handlers/__init__.py +0 -0
  30. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/result_handlers/bigquery.py +0 -0
  31. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/result_handlers/text.py +0 -0
  32. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/schema_validation.py +0 -0
  33. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/secret_manager.py +0 -0
  34. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/state_manager.py +0 -0
  35. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/data_validation/validation_builder.py +0 -0
  36. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  37. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  38. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/google_pso_data_validator.egg-info/requires.txt +0 -0
  39. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  40. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/setup.cfg +0 -0
  41. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  42. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_addon/api.py +0 -0
  43. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_addon/operations.py +0 -0
  44. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  45. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  46. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  47. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
  48. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  49. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  50. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  51. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  52. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  53. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
  54. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_db2/api.py +0 -0
  55. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  56. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
  57. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_db2/registry.py +0 -0
  58. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
  59. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_impala/api.py +0 -0
  60. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
  61. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  62. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  63. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
  64. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  65. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
  66. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  67. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  68. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  69. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  70. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
  71. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  72. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  73. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  74. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  75. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
  76. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  77. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  78. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  79. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  80. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  81. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
  82. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  83. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  84. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  85. {google_pso_data_validator-6.3.0 → google_pso_data_validator-6.4.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-pso-data-validator
3
- Version: 6.3.0
3
+ Version: 6.4.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
@@ -24,7 +24,9 @@ from data_validation import (
24
24
  cli_tools,
25
25
  clients,
26
26
  consts,
27
+ exceptions,
27
28
  state_manager,
29
+ util,
28
30
  )
29
31
  from data_validation.config_manager import ConfigManager
30
32
  from data_validation.data_validation import DataValidation
@@ -220,8 +222,8 @@ def _get_calculated_config(args, config_manager: ConfigManager) -> List[dict]:
220
222
  def _get_comparison_config(args, config_manager: ConfigManager) -> List[dict]:
221
223
  col_list = (
222
224
  None
223
- if config_manager.comparison_fields == "*"
224
- else cli_tools.get_arg_list(config_manager.comparison_fields)
225
+ if args.comparison_fields == "*"
226
+ else cli_tools.get_arg_list(args.comparison_fields)
225
227
  )
226
228
  comparison_fields = config_manager.build_comp_fields(
227
229
  col_list,
@@ -331,22 +333,25 @@ def build_config_managers_from_args(
331
333
  args: Namespace, validate_cmd: str = None
332
334
  ) -> List[ConfigManager]:
333
335
  """Return a list of config managers ready to execute."""
334
- configs = []
335
336
 
336
- # Get pre build configs to build ConfigManager objects
337
- pre_build_configs_list = cli_tools.get_pre_build_configs(args, validate_cmd)
337
+ def _build_configs():
338
+ configs = []
338
339
 
339
- # Build a list of ConfigManager objects
340
- for pre_build_configs in pre_build_configs_list:
341
- config_manager = ConfigManager.build_config_manager(**pre_build_configs)
340
+ # Get pre build configs to build ConfigManager objects
341
+ pre_build_configs_list = cli_tools.get_pre_build_configs(args, validate_cmd)
342
342
 
343
- # Append post build configs to ConfigManager object
344
- config_manager = build_config_from_args(args, config_manager)
343
+ # Build a list of ConfigManager objects
344
+ for pre_build_configs in pre_build_configs_list:
345
+ config_manager = ConfigManager.build_config_manager(**pre_build_configs)
345
346
 
346
- # Append ConfigManager object to configs list
347
- configs.append(config_manager)
347
+ # Append post build configs to ConfigManager object
348
+ config_manager = build_config_from_args(args, config_manager)
348
349
 
349
- return configs
350
+ # Append ConfigManager object to configs list
351
+ configs.append(config_manager)
352
+ return configs
353
+
354
+ return util.timed_call("Build config", _build_configs)
350
355
 
351
356
 
352
357
  def config_runner(args):
@@ -388,9 +393,26 @@ def config_runner(args):
388
393
  )
389
394
  config_file_names = cli_tools.list_validations(config_dir=args.config_dir)
390
395
  config_managers = []
396
+ errors = False
391
397
  for file in config_file_names:
392
398
  config_managers = build_config_managers_from_yaml(args, file)
393
- run_validations(args, config_managers)
399
+ try:
400
+ logging.info(
401
+ "Currently running the validation for YAML file: %s",
402
+ file,
403
+ )
404
+ run_validations(args, config_managers)
405
+ except Exception as e:
406
+ errors = True
407
+ logging.error(
408
+ "Error '%s' occurred while running config file %s. Skipping it for now.",
409
+ str(e),
410
+ file,
411
+ )
412
+ if errors:
413
+ raise exceptions.ValidationException(
414
+ "Some of the validations raised an exception"
415
+ )
394
416
  else:
395
417
  if args.kube_completions:
396
418
  logging.warning(
@@ -543,23 +565,7 @@ def run_validations(args, config_managers):
543
565
  """
544
566
  # TODO(issue/31): Add parallel execution logic
545
567
  for config_manager in config_managers:
546
- if config_manager.config and consts.CONFIG_FILE in config_manager.config:
547
- logging.info(
548
- "Currently running the validation for YAML file: %s",
549
- config_manager.config[consts.CONFIG_FILE],
550
- )
551
- try:
552
- run_validation(
553
- config_manager, dry_run=args.dry_run, verbose=args.verbose
554
- )
555
- except Exception as e:
556
- logging.error(
557
- "Error %s occurred while running config file %s. Skipping it for now.",
558
- str(e),
559
- config_manager.config[consts.CONFIG_FILE],
560
- )
561
- else:
562
- run_validation(config_manager, dry_run=args.dry_run, verbose=args.verbose)
568
+ run_validation(config_manager, dry_run=args.dry_run, verbose=args.verbose)
563
569
 
564
570
 
565
571
  def store_yaml_config_file(args, config_managers):
@@ -990,6 +990,12 @@ def _add_common_arguments(
990
990
  # TODO: update if we start to support other statuses
991
991
  help="Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned",
992
992
  )
993
+ optional_arguments.add_argument(
994
+ "--run-id",
995
+ "-rid",
996
+ default=None,
997
+ help="Set a string for the run_id, if None is input then a randomly generated UUID will be used, which is the default behaviour",
998
+ )
993
999
 
994
1000
 
995
1001
  def _check_positive(value: int) -> int:
@@ -1407,8 +1413,8 @@ def get_pre_build_configs(args: Namespace, validate_cmd: str) -> List[Dict]:
1407
1413
  result_handler_config = None
1408
1414
 
1409
1415
  # Set filter_config and threshold. Not supported in case of schema validation
1410
- filter_config = getattr(args, "filters", [])
1411
- threshold = getattr(args, "threshold", 0.0)
1416
+ filter_config = getattr(args, consts.CONFIG_FILTERS, [])
1417
+ threshold = getattr(args, consts.CONFIG_THRESHOLD, 0.0)
1412
1418
 
1413
1419
  # Get labels
1414
1420
  if args.labels is None:
@@ -1425,8 +1431,10 @@ def get_pre_build_configs(args: Namespace, validate_cmd: str) -> List[Dict]:
1425
1431
  format = args.format if args.format else "table"
1426
1432
 
1427
1433
  # Get random row arguments. Only in row validations these attributes can be present.
1434
+ # Bad coding here, but keeping it so as not to introduce a breaking change. See
1435
+ # consts.py Line 17 for a more detailed explanation.
1428
1436
  use_random_rows = getattr(args, "use_random_row", False)
1429
- random_row_batch_size = getattr(args, "random_row_batch_size", None)
1437
+ random_row_batch_size = getattr(args, consts.CONFIG_RANDOM_ROW_BATCH_SIZE, None)
1430
1438
 
1431
1439
  # Get table list. Not supported in case of custom query validation
1432
1440
  is_filesystem = source_client._source_type == "FileSystem"
@@ -1459,23 +1467,28 @@ def get_pre_build_configs(args: Namespace, validate_cmd: str) -> List[Dict]:
1459
1467
  for table_obj in tables_list:
1460
1468
  pre_build_configs = {
1461
1469
  "config_type": config_type,
1462
- "source_conn_name": args.source_conn,
1463
- "target_conn_name": args.target_conn,
1470
+ consts.CONFIG_SOURCE_CONN_NAME: args.source_conn,
1471
+ consts.CONFIG_TARGET_CONN_NAME: args.target_conn,
1464
1472
  "table_obj": table_obj,
1465
- "labels": labels,
1466
- "threshold": threshold,
1467
- "format": format,
1468
- "use_random_rows": use_random_rows,
1469
- "random_row_batch_size": random_row_batch_size,
1473
+ consts.CONFIG_LABELS: labels,
1474
+ consts.CONFIG_THRESHOLD: threshold,
1475
+ consts.CONFIG_FORMAT: format,
1476
+ consts.CONFIG_USE_RANDOM_ROWS: use_random_rows,
1477
+ consts.CONFIG_RANDOM_ROW_BATCH_SIZE: random_row_batch_size,
1470
1478
  "source_client": source_client,
1471
1479
  "target_client": target_client,
1472
1480
  "result_handler_config": result_handler_config,
1473
1481
  "filter_config": filter_config,
1474
- "filter_status": filter_status,
1475
- "trim_string_pks": getattr(args, "trim_string_pks", False),
1476
- "case_insensitive_match": getattr(args, "case_insensitive_match", False),
1482
+ consts.CONFIG_FILTER_STATUS: filter_status,
1483
+ consts.CONFIG_TRIM_STRING_PKS: getattr(
1484
+ args, consts.CONFIG_TRIM_STRING_PKS, False
1485
+ ),
1486
+ consts.CONFIG_CASE_INSENSITIVE_MATCH: getattr(
1487
+ args, consts.CONFIG_CASE_INSENSITIVE_MATCH, False
1488
+ ),
1477
1489
  consts.CONFIG_ROW_CONCAT: getattr(args, consts.CONFIG_ROW_CONCAT, None),
1478
1490
  consts.CONFIG_ROW_HASH: getattr(args, consts.CONFIG_ROW_HASH, None),
1491
+ consts.CONFIG_RUN_ID: getattr(args, consts.CONFIG_RUN_ID, None),
1479
1492
  "verbose": args.verbose,
1480
1493
  }
1481
1494
  if (
@@ -245,6 +245,11 @@ class ConfigManager(object):
245
245
  """Return field from Config"""
246
246
  return self._config.get(consts.CONFIG_ROW_HASH, [])
247
247
 
248
+ @property
249
+ def run_id(self):
250
+ """Return field from Config"""
251
+ return self._config.get(consts.CONFIG_RUN_ID, None)
252
+
248
253
  @property
249
254
  def filters(self):
250
255
  """Return Filters from Config"""
@@ -504,6 +509,7 @@ class ConfigManager(object):
504
509
  case_insensitive_match=None,
505
510
  concat=None,
506
511
  hash=None,
512
+ run_id=None,
507
513
  verbose=False,
508
514
  ):
509
515
  if isinstance(filter_config, dict):
@@ -536,6 +542,7 @@ class ConfigManager(object):
536
542
  consts.CONFIG_CASE_INSENSITIVE_MATCH: case_insensitive_match,
537
543
  consts.CONFIG_ROW_CONCAT: concat,
538
544
  consts.CONFIG_ROW_HASH: hash,
545
+ consts.CONFIG_RUN_ID: run_id,
539
546
  }
540
547
 
541
548
  return ConfigManager(
@@ -1160,7 +1167,7 @@ class ConfigManager(object):
1160
1167
  col_names.append(col)
1161
1168
  return col_names
1162
1169
 
1163
- def build_comp_fields(self, col_list: list, exclude_cols: bool = False) -> list:
1170
+ def build_comp_fields(self, col_list: list, exclude_cols: bool = False) -> dict:
1164
1171
  """This is a utility function processing comp-fields values like we do for hash/concat."""
1165
1172
  source_table = self.get_source_ibis_calculated_table()
1166
1173
  casefold_source_columns = {_.casefold(): str(_) for _ in source_table.columns}
@@ -14,6 +14,12 @@
14
14
 
15
15
 
16
16
  # Configuration Fields
17
+ # These are all keys into the config dict. Where these values come from the command line, these would
18
+ # match with the command line argument - so --secret-manager-type value would be inserted with the
19
+ # 'secret_manager_type' key.
20
+ # This is true for the most part, except where a typo gets introduced, i.e. --use-random-row became use_random_rows
21
+ # Fixing this would be a breaking change. A future cleanup to prevent this would be to use
22
+ # f'--{config_constant.repl('_','-')}' as the name argument to argparse.
17
23
  SOURCE_TYPE = "source_type"
18
24
  SECRET_MANAGER_TYPE = "secret_manager_type"
19
25
  SECRET_MANAGER_PROJECT_ID = "secret_manager_project_id"
@@ -49,6 +55,7 @@ CONFIG_TRIM_STRING_PKS = "trim_string_pks"
49
55
  CONFIG_CASE_INSENSITIVE_MATCH = "case_insensitive_match"
50
56
  CONFIG_ROW_CONCAT = "concat"
51
57
  CONFIG_ROW_HASH = "hash"
58
+ CONFIG_RUN_ID = "run_id"
52
59
  CONFIG_SOURCE_COLUMN = "source_column"
53
60
  CONFIG_TARGET_COLUMN = "target_column"
54
61
  CONFIG_THRESHOLD = "threshold"
@@ -16,11 +16,11 @@ import json
16
16
  import logging
17
17
  import warnings
18
18
  from concurrent.futures import ThreadPoolExecutor
19
-
20
19
  import ibis.backends.pandas
21
20
  import pandas
21
+ import uuid
22
22
 
23
- from data_validation import combiner, consts, metadata
23
+ from data_validation import combiner, consts, metadata, util
24
24
  from data_validation.config_manager import ConfigManager
25
25
  from data_validation.query_builder.random_row_builder import RandomRowBuilder
26
26
  from data_validation.schema_validation import SchemaValidation
@@ -63,6 +63,9 @@ class DataValidation(object):
63
63
  self.run_metadata = metadata.RunMetadata()
64
64
  self.run_metadata.labels = self.config_manager.labels
65
65
 
66
+ # Use a generated uuid for the run_id if None was supplied via config
67
+ self.run_metadata.run_id = self.config_manager.run_id or str(uuid.uuid4())
68
+
66
69
  # Initialize Validation Builder if None was supplied
67
70
  self.validation_builder = validation_builder or ValidationBuilder(
68
71
  self.config_manager
@@ -88,7 +91,7 @@ class DataValidation(object):
88
91
  """Execute Queries and Store Results"""
89
92
  # Apply random row filter before validations run
90
93
  if self.config_manager.use_random_rows():
91
- self._add_random_row_filter()
94
+ util.timed_call("Random row filter", self._add_random_row_filter)
92
95
 
93
96
  # Run correct execution for the given validation type
94
97
  if self.config_manager.validation_type == consts.ROW_VALIDATION:
@@ -98,7 +101,9 @@ class DataValidation(object):
98
101
  )
99
102
  elif self.config_manager.validation_type == consts.SCHEMA_VALIDATION:
100
103
  """Perform only schema validation"""
101
- result_df = self.schema_validator.execute()
104
+ result_df = util.timed_call(
105
+ "Schema validation", self.schema_validator.execute
106
+ )
102
107
  else:
103
108
  result_df = self._execute_validation(
104
109
  self.validation_builder, process_in_memory=True
@@ -289,6 +294,7 @@ class DataValidation(object):
289
294
 
290
295
  def _execute_validation(self, validation_builder, process_in_memory=True):
291
296
  """Execute Against a Supplied Validation Builder"""
297
+
292
298
  self.run_metadata.validations = validation_builder.get_metadata()
293
299
 
294
300
  source_query = validation_builder.get_source_query()
@@ -319,12 +325,18 @@ class DataValidation(object):
319
325
  # Submit the two query network calls concurrently
320
326
  futures.append(
321
327
  executor.submit(
322
- self.config_manager.source_client.execute, source_query
328
+ util.timed_call,
329
+ "Source query",
330
+ self.config_manager.source_client.execute,
331
+ source_query,
323
332
  )
324
333
  )
325
334
  futures.append(
326
335
  executor.submit(
327
- self.config_manager.target_client.execute, target_query
336
+ util.timed_call,
337
+ "Target query",
338
+ self.config_manager.target_client.execute,
339
+ target_query,
328
340
  )
329
341
  )
330
342
  source_df = futures[0].result()
@@ -335,7 +347,9 @@ class DataValidation(object):
335
347
  )
336
348
 
337
349
  try:
338
- result_df = combiner.generate_report(
350
+ result_df = util.timed_call(
351
+ "Generate report",
352
+ combiner.generate_report,
339
353
  pandas_client,
340
354
  self.run_metadata,
341
355
  pandas_client.table(combiner.DEFAULT_SOURCE),
@@ -17,5 +17,9 @@ class DataClientConnectionFailure(Exception):
17
17
  pass
18
18
 
19
19
 
20
- class SchemaValidationException(Exception):
20
+ class ValidationException(Exception):
21
+ pass
22
+
23
+
24
+ class SchemaValidationException(ValidationException):
21
25
  pass
@@ -14,11 +14,9 @@
14
14
 
15
15
  """Metadata classes with data about the validation run."""
16
16
 
17
-
18
17
  import dataclasses
19
18
  import datetime
20
19
  import typing
21
- import uuid
22
20
 
23
21
  from data_validation import consts
24
22
 
@@ -64,7 +62,7 @@ class ValidationMetadata(object):
64
62
 
65
63
  @dataclasses.dataclass
66
64
  class RunMetadata(object):
67
- run_id: str = dataclasses.field(default_factory=lambda: str(uuid.uuid4()))
65
+ run_id: str = dataclasses.field(default_factory=str)
68
66
  validations: dict = dataclasses.field(default_factory=dict)
69
67
  labels: list = dataclasses.field(default_factory=list)
70
68
  start_time: typing.Optional[datetime.datetime] = dataclasses.field(
@@ -0,0 +1,24 @@
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import logging
16
+ import time
17
+
18
+
19
+ def timed_call(log_txt, fn, *args, **kwargs):
20
+ t0 = time.time()
21
+ result = fn(*args, **kwargs)
22
+ elapsed = time.time() - t0
23
+ logging.debug(f"{log_txt} elapsed: {round(elapsed,2)}s")
24
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-pso-data-validator
3
- Version: 6.3.0
3
+ Version: 6.4.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
@@ -21,6 +21,7 @@ data_validation/partition_builder.py
21
21
  data_validation/schema_validation.py
22
22
  data_validation/secret_manager.py
23
23
  data_validation/state_manager.py
24
+ data_validation/util.py
24
25
  data_validation/validation_builder.py
25
26
  data_validation/query_builder/__init__.py
26
27
  data_validation/query_builder/partition_row_builder.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 = "6.3.0"
21
+ version = "6.4.0"
22
22
  release_status = "Development Status :: 4 - Beta"
23
23
 
24
24
  with open("README.md", "r") as fh:
@@ -108,6 +108,16 @@ class Backend(BaseAlchemyBackend):
108
108
  sa_url,
109
109
  poolclass=sa.pool.StaticPool,
110
110
  arraysize=self.arraysize,
111
+ # The hardcoding of 128 below is not great but is the simplest way of dealing with:
112
+ # https://github.com/GoogleCloudPlatform/professional-services-data-validator/issues/1250
113
+ # There is a quirk of SQLAlchemy behaviour that is discussed in comments of issue 1250 that
114
+ # we cannot easily deal with, even if we "fixed" it it could be months or years before we can
115
+ # take advantage of it.
116
+ # We manage the max length of identifiers generated by DVT in clients.get_max_column_length()
117
+ # and in all other cases re-use existing column names (i.e. we know the length is already fine).
118
+ # Therefore the ugly hardcoding of 128 kicks the can down the road and unblocks a customer
119
+ # who is working with Oracle 11g and a max identifier length of 30.
120
+ max_identifier_length=128,
111
121
  )
112
122
  try:
113
123
  # Identify the session in Oracle as DVT, no-op if this fails.