google-pso-data-validator 7.3.0__tar.gz → 7.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 (90) hide show
  1. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/PKG-INFO +3 -1
  2. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/README.md +2 -0
  3. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/__main__.py +8 -5
  4. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/cli_tools.py +17 -7
  5. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/config_manager.py +19 -11
  6. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/consts.py +8 -1
  7. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/partition_builder.py +6 -4
  8. google_pso_data_validator-7.4.0/data_validation/raw_query.py +74 -0
  9. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/result_handlers/bigquery.py +3 -3
  10. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/result_handlers/text.py +6 -4
  11. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/google_pso_data_validator.egg-info/PKG-INFO +3 -1
  12. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/setup.py +1 -1
  13. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_postgres/registry.py +8 -2
  14. google_pso_data_validator-7.3.0/data_validation/raw_query.py +0 -47
  15. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/LICENSE +0 -0
  16. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/__init__.py +0 -0
  17. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/app.py +0 -0
  18. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/client_info.py +0 -0
  19. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/clients.py +0 -0
  20. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/combiner.py +0 -0
  21. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/data_validation.py +0 -0
  22. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/exceptions.py +0 -0
  23. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/find_tables.py +0 -0
  24. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/gcs_helper.py +0 -0
  25. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/jellyfish_distance.py +0 -0
  26. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/metadata.py +0 -0
  27. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/query_builder/__init__.py +0 -0
  28. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  29. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/query_builder/query_builder.py +0 -0
  30. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/query_builder/random_row_builder.py +0 -0
  31. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/result_handlers/__init__.py +0 -0
  32. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/schema_validation.py +0 -0
  33. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/secret_manager.py +0 -0
  34. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/state_manager.py +0 -0
  35. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/util.py +0 -0
  36. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/data_validation/validation_builder.py +0 -0
  37. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
  38. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  39. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  40. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/google_pso_data_validator.egg-info/requires.txt +0 -0
  41. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  42. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/setup.cfg +0 -0
  43. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  44. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_addon/api.py +0 -0
  45. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_addon/operations.py +0 -0
  46. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_biquery/__init__.py +0 -0
  47. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_biquery/api.py +0 -0
  48. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  49. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  50. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  51. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
  52. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  53. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  54. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  55. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  56. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  57. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
  58. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_db2/api.py +0 -0
  59. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  60. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
  61. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_db2/registry.py +0 -0
  62. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
  63. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_impala/api.py +0 -0
  64. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
  65. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  66. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  67. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
  68. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  69. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
  70. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  71. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  72. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
  73. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  74. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  75. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
  76. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  77. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  78. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  79. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  80. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
  81. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  82. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  83. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  84. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  85. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  86. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
  87. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  88. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  89. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  90. {google_pso_data_validator-7.3.0 → google_pso_data_validator-7.4.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: google-pso-data-validator
3
- Version: 7.3.0
3
+ Version: 7.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
@@ -651,6 +651,8 @@ data-validation query
651
651
  The connection name to be queried
652
652
  --query or -q QUERY
653
653
  The raw query to run against the supplied connection
654
+ [--format or -f {minimal,python}]
655
+ Format for query output (default: python)
654
656
  ```
655
657
 
656
658
  ### Building Matched Table Lists
@@ -593,6 +593,8 @@ data-validation query
593
593
  The connection name to be queried
594
594
  --query or -q QUERY
595
595
  The raw query to run against the supplied connection
596
+ [--format or -f {minimal,python}]
597
+ Format for query output (default: python)
596
598
  ```
597
599
 
598
600
  ### Building Matched Table Lists
@@ -16,9 +16,9 @@ import json
16
16
  import logging
17
17
  import os
18
18
  import sys
19
+ from typing import TYPE_CHECKING
19
20
 
20
21
  from yaml import Dumper, dump
21
- from argparse import Namespace
22
22
  from typing import List
23
23
  from data_validation import (
24
24
  cli_tools,
@@ -34,6 +34,9 @@ from data_validation.data_validation import DataValidation
34
34
  from data_validation.find_tables import find_tables_using_string_matching
35
35
  from data_validation.partition_builder import PartitionBuilder
36
36
 
37
+ if TYPE_CHECKING:
38
+ from argparse import Namespace
39
+
37
40
  # by default yaml dumps lists as pointers. This disables that feature
38
41
  Dumper.ignore_aliases = lambda *args: True
39
42
 
@@ -246,7 +249,7 @@ def _get_comparison_config(
246
249
  return config_manager.build_config_comparison_fields(comparison_fields)
247
250
 
248
251
 
249
- def build_config_from_args(args: Namespace, config_manager: ConfigManager):
252
+ def build_config_from_args(args: "Namespace", config_manager: ConfigManager):
250
253
  """This function is used to append build configs to the config manager for all validation commands and generate-table-partitions.
251
254
  Instead of having two separate commands, e.g. validate row and validate custom-query row, generate-table-partitions
252
255
  uses implicit choice of table or custom-query. A user can specify either tables or source/target query/file,
@@ -337,7 +340,7 @@ def build_config_from_args(args: Namespace, config_manager: ConfigManager):
337
340
 
338
341
 
339
342
  def build_config_managers_from_args(
340
- args: Namespace, validate_cmd: str = None
343
+ args: "Namespace", validate_cmd: str = None
341
344
  ) -> List[ConfigManager]:
342
345
  """Return a list of config managers ready to execute."""
343
346
 
@@ -596,7 +599,7 @@ def store_json_config_file(args, config_managers):
596
599
  cli_tools.store_validation(config_file_path, json_config)
597
600
 
598
601
 
599
- def partition_and_store_config_files(args: Namespace) -> None:
602
+ def partition_and_store_config_files(args: "Namespace") -> None:
600
603
  """Build multiple YAML Config files using user specified partition logic
601
604
 
602
605
  Args:
@@ -691,7 +694,7 @@ def main():
691
694
  print(find_tables_using_string_matching(args))
692
695
  elif args.command == "query":
693
696
  raw_query.print_raw_query_output(
694
- raw_query.run_raw_query_against_connection(args)
697
+ raw_query.run_raw_query_against_connection(args), format=args.output_format
695
698
  )
696
699
  elif args.command == "validate":
697
700
  validate(args)
@@ -52,8 +52,7 @@ import sys
52
52
  import uuid
53
53
  import os
54
54
  import math
55
- from argparse import Namespace
56
- from typing import Dict, List, Optional
55
+ from typing import Dict, List, Optional, TYPE_CHECKING
57
56
  from yaml import Dumper, Loader, dump, load
58
57
 
59
58
  from data_validation import (
@@ -66,6 +65,9 @@ from data_validation import (
66
65
  )
67
66
  from data_validation.validation_builder import list_to_sublists
68
67
 
68
+ if TYPE_CHECKING:
69
+ from argparse import Namespace
70
+
69
71
 
70
72
  CONNECTION_SOURCE_FIELDS = {
71
73
  "BigQuery": [
@@ -191,7 +193,7 @@ VALIDATE_SCHEMA_HELP_TEXT = "Run a schema validation"
191
193
  VALIDATE_CUSTOM_QUERY_HELP_TEXT = "Run a custom query validation"
192
194
 
193
195
 
194
- def _check_custom_query_args(parser: argparse.ArgumentParser, parsed_args: Namespace):
196
+ def _check_custom_query_args(parser: argparse.ArgumentParser, parsed_args: "Namespace"):
195
197
  # This is where we make additional checks if the arguments provided are what we expect
196
198
  # For example, only one of -tbls and custom query options can be provided
197
199
  if hasattr(parsed_args, "tables_list") and hasattr(
@@ -223,7 +225,7 @@ def _check_custom_query_args(parser: argparse.ArgumentParser, parsed_args: Names
223
225
  return # old format - only one of them is present
224
226
 
225
227
 
226
- def get_parsed_args() -> Namespace:
228
+ def get_parsed_args() -> "Namespace":
227
229
  """Return ArgParser with configured CLI arguments."""
228
230
  parser = configure_arg_parser()
229
231
  args = ["--help"] if len(sys.argv) == 1 else None
@@ -393,6 +395,14 @@ def _configure_raw_query(subparsers):
393
395
  )
394
396
  query_parser.add_argument("--conn", "-c", help="Connection name to query")
395
397
  query_parser.add_argument("--query", "-q", help="Raw query to execute")
398
+ query_parser.add_argument(
399
+ "--format",
400
+ "-f",
401
+ dest="output_format",
402
+ choices=consts.RAW_QUERY_FORMAT_TYPES,
403
+ default=consts.FORMAT_TYPE_PYTHON,
404
+ help=f"Format for query output (default: {consts.FORMAT_TYPE_PYTHON})",
405
+ )
396
406
 
397
407
 
398
408
  def _configure_validation_config_parser(subparsers):
@@ -1027,7 +1037,7 @@ def _add_common_arguments(
1027
1037
  optional_arguments.add_argument(
1028
1038
  "--format",
1029
1039
  "-fmt",
1030
- default="table",
1040
+ default=consts.FORMAT_TYPE_TABLE,
1031
1041
  help="Set the format for printing command output, Supported formats are (text, csv, json, table). Defaults "
1032
1042
  "to table",
1033
1043
  )
@@ -1443,7 +1453,7 @@ def _concat_column_count_configs(
1443
1453
  return return_list
1444
1454
 
1445
1455
 
1446
- def get_pre_build_configs(args: Namespace, validate_cmd: str) -> List[Dict]:
1456
+ def get_pre_build_configs(args: "Namespace", validate_cmd: str) -> List[Dict]:
1447
1457
  """Return a dict of configurations to build ConfigManager object"""
1448
1458
 
1449
1459
  def cols_from_arg(concat_arg: str, client, table_obj: dict, query_str: str) -> list:
@@ -1503,7 +1513,7 @@ def get_pre_build_configs(args: Namespace, validate_cmd: str) -> List[Dict]:
1503
1513
  target_client = clients.get_data_client(mgr.get_connection_config(args.target_conn))
1504
1514
 
1505
1515
  # Get format: text, csv, json, table. Default is table
1506
- format = args.format if args.format else "table"
1516
+ format = args.format if args.format else consts.FORMAT_TYPE_TABLE
1507
1517
 
1508
1518
  # Get random row arguments. Only in row validations these attributes can be present.
1509
1519
  # Bad coding here, but keeping it so as not to introduce a breaking change. See
@@ -459,7 +459,7 @@ class ConfigManager(object):
459
459
  cols_filter_list = consts.COLUMN_FILTER_LIST
460
460
  # handler that display results either to output or in a file
461
461
  return TextResultHandler(
462
- self._config.get(consts.CONFIG_FORMAT, "table"),
462
+ self._config.get(consts.CONFIG_FORMAT, consts.FORMAT_TYPE_TABLE),
463
463
  self.filter_status,
464
464
  cols_filter_list,
465
465
  )
@@ -486,7 +486,9 @@ class ConfigManager(object):
486
486
  table_id=table_id,
487
487
  credentials=credentials,
488
488
  api_endpoint=api_endpoint,
489
- text_format=self._config.get(consts.CONFIG_FORMAT, "table"),
489
+ text_format=self._config.get(
490
+ consts.CONFIG_FORMAT, consts.FORMAT_TYPE_TABLE
491
+ ),
490
492
  )
491
493
  else:
492
494
  raise ValueError(f"Unknown ResultHandler Class: {result_type}")
@@ -874,7 +876,7 @@ class ConfigManager(object):
874
876
  target_column_ibis_type: dt.DataType,
875
877
  margin: int = 0,
876
878
  ) -> bool:
877
- """Identifies Decimal columns that will cause problems in a Pandas Dataframe.
879
+ """Identifies numeric columns that will cause problems in a Pandas Dataframe.
878
880
 
879
881
  i.e. are of greater precision than a 64bit int/real can hold.
880
882
 
@@ -883,17 +885,23 @@ class ConfigManager(object):
883
885
  """
884
886
  return bool(
885
887
  (
886
- isinstance(source_column_ibis_type, dt.Decimal)
887
- and (
888
- source_column_ibis_type.precision is None
889
- or source_column_ibis_type.precision > (18 - margin)
888
+ (isinstance(source_column_ibis_type, dt.Int64) and margin > 0)
889
+ or (
890
+ isinstance(source_column_ibis_type, dt.Decimal)
891
+ and (
892
+ source_column_ibis_type.precision is None
893
+ or source_column_ibis_type.precision > (18 - margin)
894
+ )
890
895
  )
891
896
  )
892
897
  and (
893
- isinstance(target_column_ibis_type, dt.Decimal)
894
- and (
895
- target_column_ibis_type.precision is None
896
- or target_column_ibis_type.precision > (18 - margin)
898
+ (isinstance(target_column_ibis_type, dt.Int64) and margin > 0)
899
+ or (
900
+ isinstance(target_column_ibis_type, dt.Decimal)
901
+ and (
902
+ target_column_ibis_type.precision is None
903
+ or target_column_ibis_type.precision > (18 - margin)
904
+ )
897
905
  )
898
906
  )
899
907
  )
@@ -167,7 +167,14 @@ COUNT_STAR = "{count_star}"
167
167
  RESULT_TYPE_SOURCE = "source"
168
168
  RESULT_TYPE_TARGET = "target"
169
169
 
170
- FORMAT_TYPES = ["csv", "json", "table", "text"]
170
+ FORMAT_TYPE_CSV = "csv"
171
+ FORMAT_TYPE_JSON = "json"
172
+ FORMAT_TYPE_MINIMAL = "minimal"
173
+ FORMAT_TYPE_PYTHON = "python"
174
+ FORMAT_TYPE_TABLE = "table"
175
+ FORMAT_TYPE_TEXT = "text"
176
+ FORMAT_TYPES = [FORMAT_TYPE_CSV, FORMAT_TYPE_JSON, FORMAT_TYPE_TABLE, FORMAT_TYPE_TEXT]
177
+ RAW_QUERY_FORMAT_TYPES = [FORMAT_TYPE_MINIMAL, FORMAT_TYPE_PYTHON]
171
178
 
172
179
  # Text Result Handler column filter list
173
180
  COLUMN_FILTER_LIST = [
@@ -17,8 +17,7 @@ import ibis
17
17
  import pandas
18
18
  import logging
19
19
  import re
20
- from typing import List, Dict
21
- from argparse import Namespace
20
+ from typing import List, Dict, TYPE_CHECKING
22
21
 
23
22
  from data_validation import cli_tools, consts
24
23
  from data_validation.config_manager import ConfigManager
@@ -26,9 +25,12 @@ from data_validation.query_builder.partition_row_builder import PartitionRowBuil
26
25
  from data_validation.validation_builder import ValidationBuilder
27
26
  from data_validation.validation_builder import list_to_sublists
28
27
 
28
+ if TYPE_CHECKING:
29
+ from argparse import Namespace
30
+
29
31
 
30
32
  class PartitionBuilder:
31
- def __init__(self, config_managers: List[ConfigManager], args: Namespace) -> None:
33
+ def __init__(self, config_managers: List[ConfigManager], args: "Namespace") -> None:
32
34
  self.config_managers = config_managers
33
35
  self.table_count = len(config_managers)
34
36
  self.args = args
@@ -62,7 +64,7 @@ class PartitionBuilder:
62
64
  """
63
65
  # Create multiple yaml validation blocks corresponding to the filters provided
64
66
  yaml_validations = []
65
- for (source_filter, target_filter) in zip(source_filters, target_filters):
67
+ for source_filter, target_filter in zip(source_filters, target_filters):
66
68
  filter_dict = {
67
69
  "type": "custom",
68
70
  "source": source_filter,
@@ -0,0 +1,74 @@
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
+ from sqlalchemy.engine.row import Row
18
+
19
+ from data_validation import clients, consts, state_manager
20
+
21
+ if TYPE_CHECKING:
22
+ from argparse import Namespace
23
+
24
+
25
+ def run_raw_query_against_connection(args: "Namespace") -> list:
26
+ """Return results of raw query for ad hoc usage."""
27
+ mgr = state_manager.StateManager()
28
+ with clients.get_data_client_ctx(mgr.get_connection_config(args.conn)) as client:
29
+ cursor = client.raw_sql(args.query)
30
+ res = cursor.fetchall()
31
+ try:
32
+ cursor.close()
33
+ except Exception:
34
+ pass
35
+ return res
36
+
37
+
38
+ def _minimize_raw_query_output(query_output: list) -> list:
39
+ """Reduce single column output from list(tuple(_)) to list(_)"""
40
+ if not query_output:
41
+ return query_output
42
+ if isinstance(query_output[0], (list, tuple)) and len(query_output[0]) == 1:
43
+ return [_[0] for _ in query_output]
44
+ else:
45
+ return query_output
46
+
47
+
48
+ def print_raw_query_output(query_output: list, format: str = consts.FORMAT_TYPE_PYTHON):
49
+ """Print a query resultset avoiding SQLAlchemy "... (nn characters truncated) ..." behaviour.
50
+
51
+ Args:
52
+ query_output (list): A set of rows from a SQLAlchemy query.
53
+ format (str): Output format:
54
+ FORMAT_TYPE_PYTHON: Legacy format matching previous behaviour, simple print the Python object.
55
+ FORMAT_TYPE_MINIMAL: Attempt to minimize additional noise for bare minimum output.
56
+ This is useful for a specific customer who are using DVT raw queries to
57
+ generate other DVT commands and don't want any escaping or extra parentheses
58
+ in the output.
59
+ """
60
+
61
+ def row_to_tuple(row) -> tuple:
62
+ """This prevents SQLAlchemy string truncation inside Row() objects by first converting them to a tuple."""
63
+ if isinstance(row, (Row, list)):
64
+ return tuple(row)
65
+ else:
66
+ return row
67
+
68
+ sanitized_output = [row_to_tuple(_) for _ in query_output or []]
69
+
70
+ if format == consts.FORMAT_TYPE_MINIMAL:
71
+ for row in _minimize_raw_query_output(sanitized_output):
72
+ print(row)
73
+ else:
74
+ print(sanitized_output)
@@ -16,7 +16,7 @@
16
16
 
17
17
  import logging
18
18
 
19
- from data_validation import clients
19
+ from data_validation import clients, consts
20
20
  from data_validation.result_handlers import text as text_handler
21
21
 
22
22
 
@@ -39,7 +39,7 @@ class BigQueryResultHandler(object):
39
39
  bigquery_client,
40
40
  status_list: list = None,
41
41
  table_id: str = "pso_data_validator.results",
42
- text_format: str = "table",
42
+ text_format: str = consts.FORMAT_TYPE_TABLE,
43
43
  ):
44
44
  self._bigquery_client = bigquery_client
45
45
  self._table_id = table_id
@@ -53,7 +53,7 @@ class BigQueryResultHandler(object):
53
53
  table_id: str = "pso_data_validator.results",
54
54
  credentials=None,
55
55
  api_endpoint: str = None,
56
- text_format: str = "table",
56
+ text_format: str = consts.FORMAT_TYPE_TABLE,
57
57
  ):
58
58
  """Return BigQueryResultHandler instance for given project.
59
59
 
@@ -35,15 +35,17 @@ def filter_validation_status(status_list, result_df: "DataFrame"):
35
35
 
36
36
 
37
37
  def get_formatted(
38
- result_df: "DataFrame", format: str = "table", cols_filter_list: list = None
38
+ result_df: "DataFrame",
39
+ format: str = consts.FORMAT_TYPE_TABLE,
40
+ cols_filter_list: list = None,
39
41
  ) -> str:
40
42
  """Expose formatting logic so it can be used in BigQuery handler."""
41
43
  cols_filter_list = cols_filter_list or consts.COLUMN_FILTER_LIST
42
- if format == "text":
44
+ if format == consts.FORMAT_TYPE_TEXT:
43
45
  return result_df.drop(cols_filter_list, axis=1).to_string(index=False)
44
- elif format == "csv":
46
+ elif format == consts.FORMAT_TYPE_CSV:
45
47
  return result_df.to_csv(index=False, lineterminator="\n")
46
- elif format == "json":
48
+ elif format == consts.FORMAT_TYPE_JSON:
47
49
  return result_df.to_json(orient="index")
48
50
  else:
49
51
  return result_df.drop(cols_filter_list, axis=1).to_markdown(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: google-pso-data-validator
3
- Version: 7.3.0
3
+ Version: 7.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
@@ -651,6 +651,8 @@ data-validation query
651
651
  The connection name to be queried
652
652
  --query or -q QUERY
653
653
  The raw query to run against the supplied connection
654
+ [--format or -f {minimal,python}]
655
+ Format for query output (default: python)
654
656
  ```
655
657
 
656
658
  ### Building Matched Table Lists
@@ -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.3.0"
21
+ version = "7.4.0"
22
22
  release_status = "Development Status :: 4 - Beta"
23
23
 
24
24
  with open("README.md", "r") as fh:
@@ -23,6 +23,12 @@ def sa_format_hashbytes(translator, op):
23
23
 
24
24
 
25
25
  def sa_epoch_seconds(translator, op):
26
- """Override for standard ExtractEpochSeconds but catering for larger second values."""
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."""
27
30
  arg = translator.translate(op.arg)
28
- return sa.cast(sa.extract("epoch", arg), sa.BIGINT)
31
+ return sa.cast(
32
+ sa.extract("epoch", sa.func.date_trunc(sa.sql.literal_column("'second'"), arg)),
33
+ sa.BIGINT,
34
+ )
@@ -1,47 +0,0 @@
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 sqlalchemy.engine.row import Row
16
-
17
- from data_validation import clients, state_manager
18
-
19
-
20
- def run_raw_query_against_connection(args) -> list:
21
- """Return results of raw query for ad hoc usage."""
22
- mgr = state_manager.StateManager()
23
- with clients.get_data_client_ctx(mgr.get_connection_config(args.conn)) as client:
24
- cursor = client.raw_sql(args.query)
25
- res = cursor.fetchall()
26
- try:
27
- cursor.close()
28
- except Exception:
29
- pass
30
- return res
31
-
32
-
33
- def print_raw_query_output(query_output: list):
34
- """Print a query resultset avoiding SQLAlchemy "... (nn characters truncated) ..." behaviour.
35
-
36
- Args:
37
- query_output (list): A set of rows from a SQLAlchemy query.
38
- """
39
-
40
- def row_to_str(row) -> str:
41
- """This prevents SQLAlchemy string truncation inside Row() objects by first converting them to a tuple."""
42
- if isinstance(row, Row):
43
- return str(tuple(row))
44
- else:
45
- return str(row)
46
-
47
- print([row_to_str(_) for _ in query_output or []])