table-validator 0.1.9__tar.gz → 0.1.11__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 (57) hide show
  1. {table_validator-0.1.9/table_validator.egg-info → table_validator-0.1.11}/PKG-INFO +43 -5
  2. {table_validator-0.1.9 → table_validator-0.1.11}/README.md +39 -4
  3. {table_validator-0.1.9 → table_validator-0.1.11}/pyproject.toml +8 -0
  4. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/__init__.py +27 -0
  5. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/auth/azure_auth.py +22 -5
  6. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/cli/main.py +155 -1
  7. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/cli/wizard.py +257 -12
  8. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/config/schema.py +97 -0
  9. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/connectors/azure_connector.py +97 -15
  10. table_validator-0.1.9/table_validator/connectors/databricks_connector.py → table_validator-0.1.11/table_validator/connectors/base_sql_connector.py +64 -213
  11. table_validator-0.1.11/table_validator/connectors/databricks_connector.py +257 -0
  12. table_validator-0.1.11/table_validator/connectors/spark_connector.py +89 -0
  13. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/models.py +9 -0
  14. table_validator-0.1.11/table_validator/notebook.py +176 -0
  15. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/reports/excel_report.py +448 -11
  16. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/validators/catalog_validator.py +21 -15
  17. table_validator-0.1.11/table_validator/validators/mismatch_classifier.py +221 -0
  18. {table_validator-0.1.9 → table_validator-0.1.11/table_validator.egg-info}/PKG-INFO +43 -5
  19. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator.egg-info/SOURCES.txt +8 -0
  20. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator.egg-info/requires.txt +4 -0
  21. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator.egg-info/scm_file_list.json +8 -0
  22. table_validator-0.1.11/table_validator.egg-info/scm_version.json +8 -0
  23. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_catalog_validator.py +96 -1
  24. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_cli.py +342 -0
  25. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_databricks_connector.py +89 -0
  26. table_validator-0.1.11/tests/test_excel_report.py +1158 -0
  27. table_validator-0.1.11/tests/test_mismatch_classifier.py +170 -0
  28. table_validator-0.1.11/tests/test_notebook.py +254 -0
  29. table_validator-0.1.11/tests/test_package_import_without_pyspark.py +66 -0
  30. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_row_validator.py +88 -0
  31. table_validator-0.1.11/tests/test_spark_connector.py +117 -0
  32. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_wizard.py +304 -0
  33. table_validator-0.1.9/table_validator.egg-info/scm_version.json +0 -8
  34. table_validator-0.1.9/tests/test_excel_report.py +0 -417
  35. {table_validator-0.1.9 → table_validator-0.1.11}/LICENSE +0 -0
  36. {table_validator-0.1.9 → table_validator-0.1.11}/setup.cfg +0 -0
  37. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/auth/__init__.py +0 -0
  38. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/auth/databricks_auth.py +0 -0
  39. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/cli/__init__.py +0 -0
  40. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/cli/partition_prompt.py +0 -0
  41. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/cli/summary_table.py +0 -0
  42. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/config/__init__.py +0 -0
  43. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/config/manager.py +0 -0
  44. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/connectors/__init__.py +0 -0
  45. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/engine/__init__.py +0 -0
  46. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/engine/comparison_engine.py +0 -0
  47. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/reports/__init__.py +0 -0
  48. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/validators/__init__.py +0 -0
  49. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/validators/blob_discovery.py +0 -0
  50. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator/validators/row_validator.py +0 -0
  51. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator.egg-info/dependency_links.txt +0 -0
  52. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator.egg-info/entry_points.txt +0 -0
  53. {table_validator-0.1.9 → table_validator-0.1.11}/table_validator.egg-info/top_level.txt +0 -0
  54. {table_validator-0.1.9 → table_validator-0.1.11}/tests/__init__.py +0 -0
  55. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_blob_discovery.py +0 -0
  56. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_partition_prompt.py +0 -0
  57. {table_validator-0.1.9 → table_validator-0.1.11}/tests/test_report_command.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-validator
3
- Version: 0.1.9
3
+ Version: 0.1.11
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
@@ -15,6 +15,7 @@ Requires-Dist: databricks-sql-connector>=3.0
15
15
  Requires-Dist: pandas>=2.0
16
16
  Requires-Dist: numpy>=1.24
17
17
  Requires-Dist: azure-storage-blob>=12.14
18
+ Requires-Dist: azure-identity>=1.15
18
19
  Requires-Dist: openpyxl>=3.1
19
20
  Requires-Dist: pyarrow>=14.0
20
21
  Requires-Dist: PyYAML>=6.0
@@ -23,6 +24,8 @@ Provides-Extra: dev
23
24
  Requires-Dist: pytest>=7.0; extra == "dev"
24
25
  Requires-Dist: ruff>=0.4; extra == "dev"
25
26
  Requires-Dist: black>=24.0; extra == "dev"
27
+ Provides-Extra: spark
28
+ Requires-Dist: pyspark>=3.4; extra == "spark"
26
29
  Dynamic: license-file
27
30
 
28
31
  # table-validator
@@ -38,6 +41,7 @@ Currently supported sources (target is always Databricks):
38
41
  - Databricks catalog → Databricks catalog
39
42
  - Azure Blob Storage (CSV / Excel / Parquet) → Databricks table
40
43
  - Azure SQL Database → Databricks catalog
44
+ - Azure Synapse SQL pool (dedicated or serverless) → Databricks catalog
41
45
 
42
46
  More source platforms can be added behind the same connector/validator
43
47
  interfaces described below.
@@ -80,10 +84,11 @@ Interactive wizard that walks you through:
80
84
 
81
85
  1. Azure Storage account + container (optional, skip if you don't have a Blob source) and account key
82
86
  2. Azure SQL server + database (optional, skip if you don't have a SQL source) and username/password
83
- 3. Databricks workspace URL, SQL Warehouse HTTP path, and personal access token
84
- 4. Source table (catalog / schema / table)
85
- 5. Target table (catalog / schema / table)
86
- 6. Which validations to run (catalog / schema / column / row)
87
+ 3. Azure Synapse SQL pool endpoint + database (optional, skip if you don't have a Synapse source), then either a SQL login (username/password) or a Microsoft Entra ID service principal (tenant ID + client ID + client secret)
88
+ 4. Databricks workspace URL, SQL Warehouse HTTP path, and personal access token
89
+ 5. Source table (catalog / schema / table)
90
+ 6. Target table (catalog / schema / table)
91
+ 7. Which validations to run (catalog / schema / column / row)
87
92
 
88
93
  ```bash
89
94
  tablevalidator validate
@@ -104,6 +109,36 @@ tablevalidator open
104
109
 
105
110
  Opens the most recently generated report in your default spreadsheet app.
106
111
 
112
+ ## Notebook usage (zero config, inside Databricks)
113
+
114
+ From inside a Databricks notebook cell, `validate_tables()` compares two
115
+ tables using the notebook's own already-authenticated Spark session - no
116
+ workspace URL, personal access token, or SQL Warehouse HTTP path to set
117
+ up. This is a second, independent way to use the package alongside the
118
+ CLI above (pick whichever fits: the CLI for a scheduled/scripted run
119
+ against a SQL Warehouse, this for an ad-hoc in-notebook check) - both run
120
+ the exact same full-depth comparison engine.
121
+
122
+ ```python
123
+ %pip install table-validator
124
+
125
+ from table_validator import validate_tables
126
+
127
+ result = validate_tables(
128
+ "catalog1.schema1.table1",
129
+ "catalog1.schema1.table2",
130
+ )
131
+ print(result)
132
+ ```
133
+
134
+ Optional keyword arguments: `primary_key` (a real key for row-level
135
+ comparison instead of the ROW_NUMBER() fallback), `ignore_columns`,
136
+ `only_columns`, `column_map` (for a renamed column). Outside a Databricks
137
+ notebook (e.g. local development against a real Spark session), install
138
+ the `spark` extra: `pip install "table-validator[spark]"` - inside an
139
+ actual Databricks notebook, pyspark and an active session already exist,
140
+ so the bare `%pip install table-validator` above is sufficient.
141
+
107
142
  ## Quickstart (Python API)
108
143
 
109
144
  Everything the CLI does is available as a library, built from the same
@@ -158,6 +193,9 @@ Other public entry points exported from `table_validator`:
158
193
  - `ValidatorConfig`, `default_config`, `save_config`, `require_config`,
159
194
  `ConfigNotFoundError` — the same config load/save layer the CLI wizard
160
195
  uses, if you want to construct or persist configuration programmatically.
196
+ - `validate_tables` / `SparkConnector` — the notebook-native entry point
197
+ described above (lazily imported: importing `table_validator` itself
198
+ never requires `pyspark` to be installed).
161
199
 
162
200
  ## Where config and secrets are stored
163
201
 
@@ -11,6 +11,7 @@ Currently supported sources (target is always Databricks):
11
11
  - Databricks catalog → Databricks catalog
12
12
  - Azure Blob Storage (CSV / Excel / Parquet) → Databricks table
13
13
  - Azure SQL Database → Databricks catalog
14
+ - Azure Synapse SQL pool (dedicated or serverless) → Databricks catalog
14
15
 
15
16
  More source platforms can be added behind the same connector/validator
16
17
  interfaces described below.
@@ -53,10 +54,11 @@ Interactive wizard that walks you through:
53
54
 
54
55
  1. Azure Storage account + container (optional, skip if you don't have a Blob source) and account key
55
56
  2. Azure SQL server + database (optional, skip if you don't have a SQL source) and username/password
56
- 3. Databricks workspace URL, SQL Warehouse HTTP path, and personal access token
57
- 4. Source table (catalog / schema / table)
58
- 5. Target table (catalog / schema / table)
59
- 6. Which validations to run (catalog / schema / column / row)
57
+ 3. Azure Synapse SQL pool endpoint + database (optional, skip if you don't have a Synapse source), then either a SQL login (username/password) or a Microsoft Entra ID service principal (tenant ID + client ID + client secret)
58
+ 4. Databricks workspace URL, SQL Warehouse HTTP path, and personal access token
59
+ 5. Source table (catalog / schema / table)
60
+ 6. Target table (catalog / schema / table)
61
+ 7. Which validations to run (catalog / schema / column / row)
60
62
 
61
63
  ```bash
62
64
  tablevalidator validate
@@ -77,6 +79,36 @@ tablevalidator open
77
79
 
78
80
  Opens the most recently generated report in your default spreadsheet app.
79
81
 
82
+ ## Notebook usage (zero config, inside Databricks)
83
+
84
+ From inside a Databricks notebook cell, `validate_tables()` compares two
85
+ tables using the notebook's own already-authenticated Spark session - no
86
+ workspace URL, personal access token, or SQL Warehouse HTTP path to set
87
+ up. This is a second, independent way to use the package alongside the
88
+ CLI above (pick whichever fits: the CLI for a scheduled/scripted run
89
+ against a SQL Warehouse, this for an ad-hoc in-notebook check) - both run
90
+ the exact same full-depth comparison engine.
91
+
92
+ ```python
93
+ %pip install table-validator
94
+
95
+ from table_validator import validate_tables
96
+
97
+ result = validate_tables(
98
+ "catalog1.schema1.table1",
99
+ "catalog1.schema1.table2",
100
+ )
101
+ print(result)
102
+ ```
103
+
104
+ Optional keyword arguments: `primary_key` (a real key for row-level
105
+ comparison instead of the ROW_NUMBER() fallback), `ignore_columns`,
106
+ `only_columns`, `column_map` (for a renamed column). Outside a Databricks
107
+ notebook (e.g. local development against a real Spark session), install
108
+ the `spark` extra: `pip install "table-validator[spark]"` - inside an
109
+ actual Databricks notebook, pyspark and an active session already exist,
110
+ so the bare `%pip install table-validator` above is sufficient.
111
+
80
112
  ## Quickstart (Python API)
81
113
 
82
114
  Everything the CLI does is available as a library, built from the same
@@ -131,6 +163,9 @@ Other public entry points exported from `table_validator`:
131
163
  - `ValidatorConfig`, `default_config`, `save_config`, `require_config`,
132
164
  `ConfigNotFoundError` — the same config load/save layer the CLI wizard
133
165
  uses, if you want to construct or persist configuration programmatically.
166
+ - `validate_tables` / `SparkConnector` — the notebook-native entry point
167
+ described above (lazily imported: importing `table_validator` itself
168
+ never requires `pyspark` to be installed).
134
169
 
135
170
  ## Where config and secrets are stored
136
171
 
@@ -19,6 +19,7 @@ dependencies = [
19
19
  "pandas>=2.0",
20
20
  "numpy>=1.24",
21
21
  "azure-storage-blob>=12.14",
22
+ "azure-identity>=1.15",
22
23
  "openpyxl>=3.1",
23
24
  "pyarrow>=14.0",
24
25
  "PyYAML>=6.0",
@@ -31,6 +32,13 @@ dev = [
31
32
  "ruff>=0.4",
32
33
  "black>=24.0",
33
34
  ]
35
+ # Only needed to construct SparkConnector/call validate_tables() OUTSIDE a
36
+ # real Databricks notebook (e.g. local dev/testing) - inside an actual
37
+ # notebook, pyspark and an active SparkSession already exist ambiently, so
38
+ # a bare `pip install table-validator` is sufficient there.
39
+ spark = [
40
+ "pyspark>=3.4",
41
+ ]
34
42
 
35
43
  [project.scripts]
36
44
  tablevalidator = "table_validator.cli.main:app"
@@ -43,4 +43,31 @@ __all__ = [
43
43
  # Core request/response models
44
44
  "CatalogValidationRequest",
45
45
  "CatalogValidationResponse",
46
+ # Notebook-native API (lazily loaded - see __getattr__ below; pyspark
47
+ # is only imported when one of these two names is actually accessed).
48
+ "SparkConnector",
49
+ "validate_tables",
46
50
  ]
51
+
52
+
53
+ def __getattr__(name: str):
54
+ """
55
+ PEP 562 module-level lazy attribute access, for the two names above
56
+ that transitively depend on pyspark. pyspark is NOT a hard dependency
57
+ of this package (CLI-only users never install it) - deferring the
58
+ import to actual first-access time, rather than importing eagerly
59
+ like everything else in this file, keeps `import table_validator`
60
+ working with no pyspark installed at all. Every other name in
61
+ __all__ is already bound by the eager imports above and resolved by
62
+ normal attribute lookup before Python ever calls this function -
63
+ __getattr__ only runs as a fallback for names lookup didn't resolve.
64
+ """
65
+ if name == "SparkConnector":
66
+ from table_validator.connectors.spark_connector import SparkConnector
67
+
68
+ return SparkConnector
69
+ if name == "validate_tables":
70
+ from table_validator.notebook import validate_tables
71
+
72
+ return validate_tables
73
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -26,22 +26,36 @@ class AzureCredential:
26
26
 
27
27
  storage_account_key authenticates AzureConnector (Blob Storage);
28
28
  sql_username/sql_password authenticate AzureSqlConnector (Azure SQL
29
- Database). Both are optional here since a given validation run may
30
- only need one of the two.
29
+ Database); synapse_username/synapse_password authenticate the same
30
+ AzureSqlConnector class when pointed at a Synapse SQL pool instead
31
+ (Synapse SQL is protocol-identical T-SQL/ODBC, so no separate
32
+ connector class exists - only a separate credential pair, since a
33
+ Synapse SQL login is a distinct principal from an Azure SQL DB one).
34
+ synapse_client_secret is the alternative to that pair: the Entra ID
35
+ service-principal secret used when config.azure.synapse_auth_mode is
36
+ 'entra_service_principal', paired with the non-secret tenant_id /
37
+ synapse_client_id that live in config.yaml.
38
+ All fields are optional here since a given validation run may only
39
+ need one pair.
31
40
  """
32
41
 
33
42
  storage_account_key: Optional[str] = None
34
43
  sql_username: Optional[str] = None
35
44
  sql_password: Optional[str] = None
45
+ synapse_username: Optional[str] = None
46
+ synapse_password: Optional[str] = None
47
+ synapse_client_secret: Optional[str] = None
36
48
 
37
49
 
38
50
  def get_azure_credential(config: ValidatorConfig, env_path: Path = ENV_PATH) -> AzureCredential:
39
51
  """
40
52
  Resolve Azure credentials for the given config.
41
53
 
42
- Phase 1: reads AZURE_STORAGE_KEY / AZURE_SQL_USERNAME / AZURE_SQL_PASSWORD
43
- from ~/.table_validator/.env. A later phase can swap this body for
44
- Azure CLI / Service Principal auth without changing any caller.
54
+ Phase 1: reads AZURE_STORAGE_KEY / AZURE_SQL_USERNAME /
55
+ AZURE_SQL_PASSWORD / SYNAPSE_USERNAME / SYNAPSE_PASSWORD /
56
+ SYNAPSE_CLIENT_SECRET from ~/.table_validator/.env. A later phase can
57
+ swap this body for Azure CLI / managed-identity auth without changing
58
+ any caller.
45
59
  """
46
60
  values = dotenv_values(env_path) if env_path.exists() else {}
47
61
 
@@ -49,4 +63,7 @@ def get_azure_credential(config: ValidatorConfig, env_path: Path = ENV_PATH) ->
49
63
  storage_account_key=values.get("AZURE_STORAGE_KEY") or None,
50
64
  sql_username=values.get("AZURE_SQL_USERNAME") or None,
51
65
  sql_password=values.get("AZURE_SQL_PASSWORD") or None,
66
+ synapse_username=values.get("SYNAPSE_USERNAME") or None,
67
+ synapse_password=values.get("SYNAPSE_PASSWORD") or None,
68
+ synapse_client_secret=values.get("SYNAPSE_CLIENT_SECRET") or None,
52
69
  )
@@ -19,7 +19,12 @@ from table_validator.cli.summary_table import (
19
19
  from table_validator.cli.partition_prompt import build_partition_prompt
20
20
  from table_validator.cli.wizard import run_configure_wizard
21
21
  from table_validator.config.manager import CONFIG_PATH, ConfigNotFoundError, require_config
22
- from table_validator.config.schema import SourceType, ValidationType, ValidatorConfig
22
+ from table_validator.config.schema import (
23
+ SourceType,
24
+ SynapseAuthMode,
25
+ ValidationType,
26
+ ValidatorConfig,
27
+ )
23
28
  from table_validator.connectors.azure_connector import AzureConnector, AzureSqlConnector
24
29
  from table_validator.connectors.databricks_connector import DatabricksConnector
25
30
  from table_validator.models import (
@@ -207,6 +212,18 @@ def validate(
207
212
  "prompt on its own, but --yes makes that explicit."
208
213
  ),
209
214
  ),
215
+ skip_category_summary: bool = typer.Option(
216
+ False,
217
+ "--skip-category-summary",
218
+ help=(
219
+ "Omit the 'Mismatch Categories' sheet (root-cause "
220
+ "classification + plain-English summary of Data Mismatches) "
221
+ "from the Excel report. Every other sheet, including Data "
222
+ "Mismatches itself, is unaffected - this recovers the exact "
223
+ "report shape from before the mismatch-categorization "
224
+ "feature existed, for anyone who doesn't want it."
225
+ ),
226
+ ),
210
227
  ) -> None:
211
228
  """Run validation and produce an Excel validation report."""
212
229
 
@@ -281,6 +298,8 @@ def validate(
281
298
  result = _run_blob_validation(config, azure_credential, databricks)
282
299
  elif config.source_type == SourceType.AZURE_SQL:
283
300
  result = _run_sql_validation(config, azure_credential, databricks)
301
+ elif config.source_type == SourceType.SYNAPSE:
302
+ result = _run_synapse_validation(config, azure_credential, databricks)
284
303
  else:
285
304
  result = _run_databricks_validation(config, databricks, mode=mode, yes=yes)
286
305
  except ValueError as exc:
@@ -310,6 +329,7 @@ def validate(
310
329
  result, str(output),
311
330
  source_type=config.source_type.value,
312
331
  enabled_validations=report_enabled_validations,
332
+ skip_category_summary=skip_category_summary,
313
333
  )
314
334
  except PermissionError:
315
335
  typer.secho(
@@ -444,6 +464,17 @@ def _run_databricks_validation(
444
464
  tables_restriction = [config.source_table.table]
445
465
  if config.source_table.table.lower() != config.target_table.table.lower():
446
466
  table_map = {config.source_table.table: config.target_table.table}
467
+ else:
468
+ # Schema-wide sweep (no single table named on both sides) - apply
469
+ # the wizard's optional only_tables allowlist / table_map renaming
470
+ # for this schema, if the user set either. Both are no-ops
471
+ # (tables_restriction stays None, table_map stays {}) unless the
472
+ # user actually configured them, so a plain schema sweep behaves
473
+ # exactly as before this feature existed.
474
+ if config.only_tables:
475
+ tables_restriction = list(config.only_tables)
476
+ if config.table_map:
477
+ table_map = dict(config.table_map)
447
478
 
448
479
  # A configured primary key only applies to the single named table
449
480
  # (not a catalog-wide sweep) - CatalogValidator looks it up by
@@ -603,6 +634,113 @@ def _run_sql_validation(
603
634
  return validator.validate(request)
604
635
 
605
636
 
637
+ def _run_synapse_validation(
638
+ config: ValidatorConfig,
639
+ azure_credential,
640
+ databricks: DatabricksConnector,
641
+ ) -> CatalogValidationResponse:
642
+ """source_type == synapse: Azure Synapse SQL pool (dedicated or
643
+ serverless) against a Databricks catalog. Synapse SQL is
644
+ protocol-identical T-SQL over the same ODBC driver Azure SQL Database
645
+ uses, so this reuses AzureSqlConnector/AzureSqlValidator UNCHANGED,
646
+ just constructed with the Synapse server/database/credentials instead
647
+ of an Azure SQL DB's - no new connector or validator class, mirrors
648
+ _run_sql_validation's logic exactly (see that function for the
649
+ schema_map/table_map/primary_keys/FULL-mode rationale, identical
650
+ here).
651
+
652
+ Auth is the one place Synapse genuinely diverges from the Azure SQL
653
+ path: config.azure.synapse_auth_mode selects between a SQL login
654
+ (SYNAPSE_USERNAME/SYNAPSE_PASSWORD) and a Microsoft Entra ID service
655
+ principal (azure.tenant_id + azure.synapse_client_id +
656
+ SYNAPSE_CLIENT_SECRET). Entra is what a workspace with SQL
657
+ authentication disabled requires - the connector fetches an access
658
+ token and passes it via the ODBC access-token attribute rather than
659
+ sending UID/PWD at all.
660
+
661
+ Not yet verified against a live Synapse SQL pool for pool-specific
662
+ T-SQL quirks (dedicated pools have historically had some system-view/
663
+ HASHBYTES-adjacent gaps vs. Azure SQL DB) - flagged here rather than
664
+ assumed away; report back if a specific query fails against your pool
665
+ and it can be special-cased in AzureSqlConnector the same way
666
+ decimal_as_integer/float formatting already are for Databricks
667
+ compatibility.
668
+ """
669
+ if not config.azure.synapse_server or not config.azure.synapse_database:
670
+ raise ValueError(
671
+ "azure.synapse_server and azure.synapse_database are required "
672
+ "for a Synapse source. Run 'tablevalidator configure' first."
673
+ )
674
+
675
+ if config.azure.synapse_auth_mode == SynapseAuthMode.ENTRA_SERVICE_PRINCIPAL:
676
+ missing_entra = []
677
+ if not config.azure.tenant_id:
678
+ missing_entra.append("azure.tenant_id (in config.yaml)")
679
+ if not config.azure.synapse_client_id:
680
+ missing_entra.append("azure.synapse_client_id (in config.yaml)")
681
+ if not azure_credential.synapse_client_secret:
682
+ missing_entra.append("SYNAPSE_CLIENT_SECRET (in ~/.table_validator/.env)")
683
+ if missing_entra:
684
+ raise ValueError(
685
+ "Synapse is configured for Entra service-principal auth but "
686
+ "these are missing: " + ", ".join(missing_entra)
687
+ + ". Run 'tablevalidator configure' first."
688
+ )
689
+
690
+ synapse = AzureSqlConnector(
691
+ server=config.azure.synapse_server,
692
+ database=config.azure.synapse_database,
693
+ tenant_id=config.azure.tenant_id,
694
+ client_id=config.azure.synapse_client_id,
695
+ client_secret=azure_credential.synapse_client_secret,
696
+ )
697
+ else:
698
+ if not azure_credential.synapse_username or not azure_credential.synapse_password:
699
+ raise ValueError(
700
+ "No Synapse username/password found in ~/.table_validator/.env. "
701
+ "Run 'tablevalidator configure' first."
702
+ )
703
+
704
+ synapse = AzureSqlConnector(
705
+ server=config.azure.synapse_server,
706
+ database=config.azure.synapse_database,
707
+ username=azure_credential.synapse_username,
708
+ password=azure_credential.synapse_password,
709
+ )
710
+
711
+ schema_map: Dict[str, str] = {}
712
+ schemas_restriction = None
713
+ if config.synapse_source.schema_name and config.target_table.schema_name:
714
+ schema_map = {config.synapse_source.schema_name: config.target_table.schema_name}
715
+ schemas_restriction = [config.synapse_source.schema_name]
716
+
717
+ table_map: Dict[str, str] = {}
718
+ tables_restriction = None
719
+ if config.synapse_source.table and config.target_table.table:
720
+ table_map = {config.synapse_source.table: config.target_table.table}
721
+ tables_restriction = [config.synapse_source.table]
722
+
723
+ primary_keys: Dict[str, list] = {}
724
+ if config.primary_key and config.target_table.table:
725
+ primary_keys[config.target_table.table] = config.primary_key
726
+ if config.target_table.schema_name:
727
+ key = f"{config.target_table.schema_name}.{config.target_table.table}"
728
+ primary_keys[key] = config.primary_key
729
+
730
+ request = AzureSqlValidationRequest(
731
+ target_catalog=config.target_table.catalog or "",
732
+ schemas=schemas_restriction,
733
+ schema_map=schema_map,
734
+ tables=tables_restriction,
735
+ table_map=table_map,
736
+ primary_keys=primary_keys,
737
+ data_compare_mode=DataCompareMode.FULL,
738
+ )
739
+
740
+ validator = AzureSqlValidator(synapse, databricks)
741
+ return validator.validate(request)
742
+
743
+
606
744
  # ---------------------------------------------------------------------------
607
745
  # Helpers
608
746
  # ---------------------------------------------------------------------------
@@ -632,6 +770,11 @@ def _missing_config_fields(config: ValidatorConfig) -> list:
632
770
  missing.append("azure.sql_server")
633
771
  if not config.azure.sql_database:
634
772
  missing.append("azure.sql_database")
773
+ elif config.source_type == SourceType.SYNAPSE:
774
+ if not config.azure.synapse_server:
775
+ missing.append("azure.synapse_server")
776
+ if not config.azure.synapse_database:
777
+ missing.append("azure.synapse_database")
635
778
  else:
636
779
  if not config.source_table.catalog:
637
780
  missing.append("source_table.catalog")
@@ -696,6 +839,17 @@ def _describe_scope(config: ValidatorConfig) -> str:
696
839
  source_desc = f"sql:{database}.{config.sql_source.schema_name} (all tables)"
697
840
  else:
698
841
  source_desc = f"sql:{database}.{config.sql_source.schema_name}.{config.sql_source.table}"
842
+ elif config.source_type == SourceType.SYNAPSE:
843
+ database = config.azure.synapse_database or "?"
844
+ if not config.synapse_source.schema_name:
845
+ source_desc = f"synapse:{database} (all schemas)"
846
+ elif not config.synapse_source.table:
847
+ source_desc = f"synapse:{database}.{config.synapse_source.schema_name} (all tables)"
848
+ else:
849
+ source_desc = (
850
+ f"synapse:{database}.{config.synapse_source.schema_name}."
851
+ f"{config.synapse_source.table}"
852
+ )
699
853
  else:
700
854
  source_desc = _side(config.source_table)
701
855