table-validator 0.1.2__tar.gz → 0.1.4__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 (48) hide show
  1. {table_validator-0.1.2/table_validator.egg-info → table_validator-0.1.4}/PKG-INFO +9 -1
  2. {table_validator-0.1.2 → table_validator-0.1.4}/README.md +8 -0
  3. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/cli/main.py +23 -4
  4. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/connectors/databricks_connector.py +29 -5
  5. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/models.py +34 -0
  6. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/validators/catalog_validator.py +422 -119
  7. {table_validator-0.1.2 → table_validator-0.1.4/table_validator.egg-info}/PKG-INFO +9 -1
  8. table_validator-0.1.4/table_validator.egg-info/scm_version.json +8 -0
  9. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_catalog_validator.py +144 -0
  10. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_cli.py +100 -0
  11. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_databricks_connector.py +78 -0
  12. table_validator-0.1.2/table_validator.egg-info/scm_version.json +0 -8
  13. {table_validator-0.1.2 → table_validator-0.1.4}/LICENSE +0 -0
  14. {table_validator-0.1.2 → table_validator-0.1.4}/pyproject.toml +0 -0
  15. {table_validator-0.1.2 → table_validator-0.1.4}/setup.cfg +0 -0
  16. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/__init__.py +0 -0
  17. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/auth/__init__.py +0 -0
  18. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/auth/azure_auth.py +0 -0
  19. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/auth/databricks_auth.py +0 -0
  20. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/cli/__init__.py +0 -0
  21. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/cli/partition_prompt.py +0 -0
  22. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/cli/summary_table.py +0 -0
  23. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/cli/wizard.py +0 -0
  24. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/config/__init__.py +0 -0
  25. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/config/manager.py +0 -0
  26. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/config/schema.py +0 -0
  27. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/connectors/__init__.py +0 -0
  28. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/connectors/azure_connector.py +0 -0
  29. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/engine/__init__.py +0 -0
  30. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/engine/comparison_engine.py +0 -0
  31. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/reports/__init__.py +0 -0
  32. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/reports/excel_report.py +0 -0
  33. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/validators/__init__.py +0 -0
  34. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/validators/blob_discovery.py +0 -0
  35. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator/validators/row_validator.py +0 -0
  36. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator.egg-info/SOURCES.txt +0 -0
  37. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator.egg-info/dependency_links.txt +0 -0
  38. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator.egg-info/entry_points.txt +0 -0
  39. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator.egg-info/requires.txt +0 -0
  40. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator.egg-info/scm_file_list.json +0 -0
  41. {table_validator-0.1.2 → table_validator-0.1.4}/table_validator.egg-info/top_level.txt +0 -0
  42. {table_validator-0.1.2 → table_validator-0.1.4}/tests/__init__.py +0 -0
  43. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_blob_discovery.py +0 -0
  44. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_excel_report.py +0 -0
  45. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_partition_prompt.py +0 -0
  46. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_report_command.py +0 -0
  47. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_row_validator.py +0 -0
  48. {table_validator-0.1.2 → table_validator-0.1.4}/tests/test_wizard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-validator
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: CLI tool for validating data migrations between Azure (Blob Storage / SQL Database) and Databricks Delta Lake catalogs
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -174,6 +174,14 @@ Everything lives outside the repo, under your home directory:
174
174
  by construction, since it's written under your home directory rather
175
175
  than the working directory.
176
176
 
177
+ Optional: `DATABRICKS_RETRY_TIMEOUT_SECONDS` (a plain environment variable,
178
+ not part of `config.yaml`) raises the CloudFetch HTTP retry timeout above
179
+ its 300-second default. On a slow or unstable network, downloading a
180
+ large row-hash result set can legitimately take longer than that, causing
181
+ a `Retry request would exceed Retry policy max retry duration` failure
182
+ even though the query itself succeeded. Set it higher (e.g. `900` for 15
183
+ minutes) if you hit this on large tables.
184
+
177
185
  A future version will replace manual credential entry with Azure CLI /
178
186
  Service Principal auth and Databricks CLI / OAuth login, without changing
179
187
  the config file format or any command usage above.
@@ -147,6 +147,14 @@ Everything lives outside the repo, under your home directory:
147
147
  by construction, since it's written under your home directory rather
148
148
  than the working directory.
149
149
 
150
+ Optional: `DATABRICKS_RETRY_TIMEOUT_SECONDS` (a plain environment variable,
151
+ not part of `config.yaml`) raises the CloudFetch HTTP retry timeout above
152
+ its 300-second default. On a slow or unstable network, downloading a
153
+ large row-hash result set can legitimately take longer than that, causing
154
+ a `Retry request would exceed Retry policy max retry duration` failure
155
+ even though the query itself succeeded. Set it higher (e.g. `900` for 15
156
+ minutes) if you hit this on large tables.
157
+
150
158
  A future version will replace manual credential entry with Azure CLI /
151
159
  Service Principal auth and Databricks CLI / OAuth login, without changing
152
160
  the config file format or any command usage above.
@@ -420,20 +420,37 @@ def _run_databricks_validation(
420
420
  matching" for that level - CatalogValidator already performs the
421
421
  list-and-intersect discovery internally whenever request.schemas/
422
422
  tables is left unrestricted (None), so leaving the restriction off
423
- IS the discovery trigger; there is no separate discovery step to call."""
423
+ IS the discovery trigger; there is no separate discovery step to call.
424
+
425
+ If the user explicitly named BOTH a source and target schema/table,
426
+ compare that exact pair directly - schema_map/table_map bypass
427
+ name-based matching entirely, so the two sides don't need to share a
428
+ name (a typo, a rename, different casing). schemas_restriction/
429
+ tables_restriction scope by the SOURCE-side name, matching
430
+ CatalogValidator._validate_schema/compare_catalogs' filtering
431
+ convention (mirrors _run_sql_validation's identical pattern below).
432
+ When names are identical (today's common case) schema_map/table_map
433
+ end up empty and behavior is unchanged."""
434
+ schema_map: Dict[str, str] = {}
424
435
  schemas_restriction = None
425
436
  if config.source_table.schema_name and config.target_table.schema_name:
426
- schemas_restriction = [config.target_table.schema_name]
437
+ schemas_restriction = [config.source_table.schema_name]
438
+ if config.source_table.schema_name.lower() != config.target_table.schema_name.lower():
439
+ schema_map = {config.source_table.schema_name: config.target_table.schema_name}
427
440
 
441
+ table_map: Dict[str, str] = {}
428
442
  tables_restriction = None
429
443
  if config.source_table.table and config.target_table.table:
430
- tables_restriction = [config.target_table.table]
444
+ tables_restriction = [config.source_table.table]
445
+ if config.source_table.table.lower() != config.target_table.table.lower():
446
+ table_map = {config.source_table.table: config.target_table.table}
431
447
 
432
448
  # A configured primary key only applies to the single named table
433
449
  # (not a catalog-wide sweep) - CatalogValidator looks it up by
434
450
  # "schema.table" first, falling back to a bare table name, so provide
435
451
  # both forms when a schema is known; falls back to row-number matching
436
- # as before when unset.
452
+ # as before when unset. Keyed by the TARGET-side name, since that's
453
+ # what _lookup_primary_key resolves against.
437
454
  primary_keys: Dict[str, list] = {}
438
455
  if config.primary_key and config.target_table.table:
439
456
  primary_keys[config.target_table.table] = config.primary_key
@@ -447,7 +464,9 @@ def _run_databricks_validation(
447
464
  source_catalog=config.source_table.catalog or "",
448
465
  target_catalog=config.target_table.catalog or "",
449
466
  schemas=schemas_restriction,
467
+ schema_map=schema_map,
450
468
  tables=tables_restriction,
469
+ table_map=table_map,
451
470
  enabled_validations=set(config.validations),
452
471
  primary_keys=primary_keys,
453
472
  max_tier=max_tier,
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  import datetime
15
15
  import logging
16
16
  import numbers
17
+ import os
17
18
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple
18
19
 
19
20
  import pandas as pd
@@ -97,6 +98,7 @@ class DatabricksConnector:
97
98
  host: Optional[str] = None,
98
99
  token: Optional[str] = None,
99
100
  http_path: Optional[str] = None,
101
+ retry_timeout_seconds: Optional[float] = None,
100
102
  ) -> None:
101
103
  """
102
104
  host/token/http_path must be resolved by the caller before
@@ -104,11 +106,27 @@ class DatabricksConnector:
104
106
  get_databricks_token() for the token, and config.databricks.
105
107
  workspace_url/http_path for the rest. This connector does not
106
108
  read credentials from the environment itself.
109
+
110
+ retry_timeout_seconds overrides databricks-sql-connector's
111
+ CloudFetch HTTP retry policy (_retry_stop_after_attempts_duration),
112
+ which otherwise defaults to 300 seconds. On a slow or unstable
113
+ network, downloading a large row-hash result set can legitimately
114
+ take longer than that, causing a hard
115
+ "Retry request would exceed Retry policy max retry duration"
116
+ failure even though the query itself succeeded - raising this
117
+ gives a slow-but-working connection more time instead of giving
118
+ up. Falls back to the DATABRICKS_RETRY_TIMEOUT_SECONDS environment
119
+ variable, then the connector's own default, if not given.
107
120
  """
108
121
 
109
122
  self._host = host
110
123
  self._token = token
111
124
  self._http_path = http_path
125
+ self._retry_timeout_seconds = retry_timeout_seconds or (
126
+ float(os.environ["DATABRICKS_RETRY_TIMEOUT_SECONDS"])
127
+ if os.environ.get("DATABRICKS_RETRY_TIMEOUT_SECONDS")
128
+ else None
129
+ )
112
130
 
113
131
  if not self._host or not self._token:
114
132
  raise ValueError(
@@ -138,11 +156,17 @@ class DatabricksConnector:
138
156
 
139
157
  try:
140
158
 
141
- self._connection = sql.connect(
142
- server_hostname=self._host,
143
- http_path=self._http_path,
144
- access_token=self._token,
145
- )
159
+ connect_kwargs: Dict[str, Any] = {
160
+ "server_hostname": self._host,
161
+ "http_path": self._http_path,
162
+ "access_token": self._token,
163
+ }
164
+ if self._retry_timeout_seconds is not None:
165
+ connect_kwargs["_retry_stop_after_attempts_duration"] = (
166
+ self._retry_timeout_seconds
167
+ )
168
+
169
+ self._connection = sql.connect(**connect_kwargs)
146
170
 
147
171
  with self._connection.cursor() as cursor:
148
172
  cursor.execute("SELECT 1")
@@ -451,6 +451,19 @@ class CatalogValidationRequest(BaseModel):
451
451
  ),
452
452
  )
453
453
 
454
+ schema_map: Dict[str, str] = Field(
455
+ default_factory=dict,
456
+ description=(
457
+ "Optional map of source-catalog schema name -> target-catalog "
458
+ "schema name, for when the user has explicitly named a source "
459
+ "and target schema that don't share the same name. An explicit "
460
+ "pair like this is compared directly, bypassing name-based "
461
+ "schema matching entirely (unlike `schemas`, which still "
462
+ "requires the name to appear in the intersection). Unmapped "
463
+ "schemas are matched by identical name as usual."
464
+ ),
465
+ )
466
+
454
467
  tables: Optional[List[str]] = Field(
455
468
  default=None,
456
469
  description=(
@@ -460,6 +473,19 @@ class CatalogValidationRequest(BaseModel):
460
473
  ),
461
474
  )
462
475
 
476
+ table_map: Dict[str, str] = Field(
477
+ default_factory=dict,
478
+ description=(
479
+ "Optional map of source-catalog table name -> target-catalog "
480
+ "table name, for when the user has explicitly named a source "
481
+ "and target table that don't share the same name - an explicit "
482
+ "pair like this is compared directly, bypassing name-based "
483
+ "table matching entirely (unlike `tables`, which still requires "
484
+ "the name to appear in the intersection). Unmapped tables are "
485
+ "matched by identical name as usual."
486
+ ),
487
+ )
488
+
463
489
  ignore_columns: List[str] = Field(default_factory=list)
464
490
 
465
491
  case_sensitive_columns: bool = Field(
@@ -835,6 +861,14 @@ class TableValidationResult(BaseModel):
835
861
  table: str
836
862
  status: ValidationStatus = ValidationStatus.SKIPPED
837
863
 
864
+ # Populated by CatalogValidator/AzureSqlValidator whenever the
865
+ # source-side schema/table name differs from the target-side name
866
+ # shown above (e.g. an explicit schema_map/table_map pair) - lets a
867
+ # future report enhancement show both names. schema_name/table above
868
+ # always reflect the TARGET-side name (the reporting convention).
869
+ source_schema_name: Optional[str] = None
870
+ source_table_name: Optional[str] = None
871
+
838
872
  exists_in_source: bool = True
839
873
  exists_in_target: bool = True
840
874