table-validator 0.1.8__tar.gz → 0.1.10__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.
- {table_validator-0.1.8/table_validator.egg-info → table_validator-0.1.10}/PKG-INFO +8 -5
- {table_validator-0.1.8 → table_validator-0.1.10}/README.md +6 -4
- {table_validator-0.1.8 → table_validator-0.1.10}/pyproject.toml +1 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/auth/azure_auth.py +22 -5
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/auth/databricks_auth.py +15 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/cli/main.py +158 -12
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/cli/wizard.py +344 -12
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/config/schema.py +114 -1
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/connectors/azure_connector.py +97 -15
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/connectors/databricks_connector.py +121 -34
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/models.py +44 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/reports/excel_report.py +448 -11
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/validators/catalog_validator.py +418 -117
- table_validator-0.1.10/table_validator/validators/mismatch_classifier.py +221 -0
- {table_validator-0.1.8 → table_validator-0.1.10/table_validator.egg-info}/PKG-INFO +8 -5
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator.egg-info/SOURCES.txt +2 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator.egg-info/requires.txt +1 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator.egg-info/scm_file_list.json +2 -0
- table_validator-0.1.10/table_validator.egg-info/scm_version.json +8 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_catalog_validator.py +695 -7
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_cli.py +349 -4
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_databricks_connector.py +187 -0
- table_validator-0.1.10/tests/test_excel_report.py +1158 -0
- table_validator-0.1.10/tests/test_mismatch_classifier.py +170 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_row_validator.py +88 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_wizard.py +520 -2
- table_validator-0.1.8/table_validator.egg-info/scm_version.json +0 -8
- table_validator-0.1.8/tests/test_excel_report.py +0 -417
- {table_validator-0.1.8 → table_validator-0.1.10}/LICENSE +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/setup.cfg +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/auth/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/cli/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/cli/partition_prompt.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/cli/summary_table.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/config/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/config/manager.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/connectors/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/engine/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/engine/comparison_engine.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/reports/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/validators/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/validators/blob_discovery.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator/validators/row_validator.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator.egg-info/dependency_links.txt +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator.egg-info/entry_points.txt +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/table_validator.egg-info/top_level.txt +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/__init__.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_blob_discovery.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/tests/test_partition_prompt.py +0 -0
- {table_validator-0.1.8 → table_validator-0.1.10}/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.
|
|
3
|
+
Version: 0.1.10
|
|
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
|
|
@@ -38,6 +39,7 @@ Currently supported sources (target is always Databricks):
|
|
|
38
39
|
- Databricks catalog → Databricks catalog
|
|
39
40
|
- Azure Blob Storage (CSV / Excel / Parquet) → Databricks table
|
|
40
41
|
- Azure SQL Database → Databricks catalog
|
|
42
|
+
- Azure Synapse SQL pool (dedicated or serverless) → Databricks catalog
|
|
41
43
|
|
|
42
44
|
More source platforms can be added behind the same connector/validator
|
|
43
45
|
interfaces described below.
|
|
@@ -80,10 +82,11 @@ Interactive wizard that walks you through:
|
|
|
80
82
|
|
|
81
83
|
1. Azure Storage account + container (optional, skip if you don't have a Blob source) and account key
|
|
82
84
|
2. Azure SQL server + database (optional, skip if you don't have a SQL source) and username/password
|
|
83
|
-
3.
|
|
84
|
-
4.
|
|
85
|
-
5.
|
|
86
|
-
6.
|
|
85
|
+
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)
|
|
86
|
+
4. Databricks workspace URL, SQL Warehouse HTTP path, and personal access token
|
|
87
|
+
5. Source table (catalog / schema / table)
|
|
88
|
+
6. Target table (catalog / schema / table)
|
|
89
|
+
7. Which validations to run (catalog / schema / column / row)
|
|
87
90
|
|
|
88
91
|
```bash
|
|
89
92
|
tablevalidator validate
|
|
@@ -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.
|
|
57
|
-
4.
|
|
58
|
-
5.
|
|
59
|
-
6.
|
|
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
|
|
@@ -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)
|
|
30
|
-
|
|
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 /
|
|
43
|
-
|
|
44
|
-
|
|
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
|
)
|
|
@@ -29,3 +29,18 @@ def get_databricks_token(config: ValidatorConfig, env_path: Path = ENV_PATH) ->
|
|
|
29
29
|
"""
|
|
30
30
|
values = dotenv_values(env_path) if env_path.exists() else {}
|
|
31
31
|
return values.get("DATABRICKS_TOKEN") or None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def host_from_workspace_url(workspace_url: Optional[str]) -> Optional[str]:
|
|
35
|
+
"""DatabricksConnector wants a bare hostname; the wizard stores a full
|
|
36
|
+
https:// workspace URL, so strip the scheme and any trailing path.
|
|
37
|
+
|
|
38
|
+
Shared by cli/main.py (building the connector for `validate`) and
|
|
39
|
+
cli/wizard.py (building a connector during `configure` for the
|
|
40
|
+
column-mapping live picker) - kept here rather than in either CLI
|
|
41
|
+
module so neither has to import from the other.
|
|
42
|
+
"""
|
|
43
|
+
if not workspace_url:
|
|
44
|
+
return None
|
|
45
|
+
host = workspace_url.replace("https://", "").replace("http://", "")
|
|
46
|
+
return host.split("/")[0]
|
|
@@ -10,7 +10,7 @@ from typing import Dict, Optional
|
|
|
10
10
|
import typer
|
|
11
11
|
|
|
12
12
|
from table_validator.auth.azure_auth import get_azure_credential
|
|
13
|
-
from table_validator.auth.databricks_auth import get_databricks_token
|
|
13
|
+
from table_validator.auth.databricks_auth import get_databricks_token, host_from_workspace_url
|
|
14
14
|
from table_validator.cli.summary_table import (
|
|
15
15
|
print_summary_table,
|
|
16
16
|
summary_from_excel,
|
|
@@ -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
|
|
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
|
|
|
@@ -256,7 +273,7 @@ def validate(
|
|
|
256
273
|
|
|
257
274
|
try:
|
|
258
275
|
databricks = DatabricksConnector(
|
|
259
|
-
host=
|
|
276
|
+
host=host_from_workspace_url(config.databricks.workspace_url),
|
|
260
277
|
token=token,
|
|
261
278
|
http_path=config.databricks.http_path,
|
|
262
279
|
)
|
|
@@ -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
|
|
@@ -473,6 +504,7 @@ def _run_databricks_validation(
|
|
|
473
504
|
only_columns=config.only_columns,
|
|
474
505
|
ignore_columns=config.ignore_columns,
|
|
475
506
|
ignore_datatype_columns=config.ignore_datatype_columns,
|
|
507
|
+
column_map=config.column_map,
|
|
476
508
|
)
|
|
477
509
|
|
|
478
510
|
partition_prompt = build_partition_prompt(yes=yes)
|
|
@@ -602,6 +634,113 @@ def _run_sql_validation(
|
|
|
602
634
|
return validator.validate(request)
|
|
603
635
|
|
|
604
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
|
+
|
|
605
744
|
# ---------------------------------------------------------------------------
|
|
606
745
|
# Helpers
|
|
607
746
|
# ---------------------------------------------------------------------------
|
|
@@ -631,6 +770,11 @@ def _missing_config_fields(config: ValidatorConfig) -> list:
|
|
|
631
770
|
missing.append("azure.sql_server")
|
|
632
771
|
if not config.azure.sql_database:
|
|
633
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")
|
|
634
778
|
else:
|
|
635
779
|
if not config.source_table.catalog:
|
|
636
780
|
missing.append("source_table.catalog")
|
|
@@ -638,15 +782,6 @@ def _missing_config_fields(config: ValidatorConfig) -> list:
|
|
|
638
782
|
return missing
|
|
639
783
|
|
|
640
784
|
|
|
641
|
-
def _host_from_workspace_url(workspace_url: Optional[str]) -> Optional[str]:
|
|
642
|
-
"""DatabricksConnector wants a bare hostname; the wizard stores a full
|
|
643
|
-
https:// workspace URL, so strip the scheme and any trailing path."""
|
|
644
|
-
if not workspace_url:
|
|
645
|
-
return None
|
|
646
|
-
host = workspace_url.replace("https://", "").replace("http://", "")
|
|
647
|
-
return host.split("/")[0]
|
|
648
|
-
|
|
649
|
-
|
|
650
785
|
def _open_in_default_app(path: Path) -> None:
|
|
651
786
|
"""
|
|
652
787
|
Launch `path` in whatever application the OS has associated with its
|
|
@@ -704,6 +839,17 @@ def _describe_scope(config: ValidatorConfig) -> str:
|
|
|
704
839
|
source_desc = f"sql:{database}.{config.sql_source.schema_name} (all tables)"
|
|
705
840
|
else:
|
|
706
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
|
+
)
|
|
707
853
|
else:
|
|
708
854
|
source_desc = _side(config.source_table)
|
|
709
855
|
|