tablevalidator-databricks 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,15 @@
1
+ """tablevalidator_databricks: generates a widget-driven Databricks notebook
2
+ UI on top of the table-validator package's validate_tables() API.
3
+
4
+ This package contains no validation logic of its own - it only writes a
5
+ notebook file whose cells call into table_validator's existing engine
6
+ (see tablevalidator_databricks/templates/widget_notebook.py.tmpl)."""
7
+
8
+ from importlib.metadata import PackageNotFoundError, version
9
+
10
+ try:
11
+ __version__ = version("tablevalidator-databricks")
12
+ except PackageNotFoundError:
13
+ __version__ = "0.0.0"
14
+
15
+ __all__ = ["__version__"]
File without changes
@@ -0,0 +1,99 @@
1
+ """CLI entry point: `tablevalidator-databricks` console script."""
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from tablevalidator_databricks.generator.generator import VALID_MODES, generate_notebook
8
+
9
+ app = typer.Typer(
10
+ name="tablevalidator-databricks",
11
+ help="Generate a widget-driven Databricks notebook UI for table-validator.",
12
+ no_args_is_help=True,
13
+ )
14
+
15
+ DEFAULT_OUTPUT_PATH = Path("TableValidator.py")
16
+
17
+
18
+ @app.command()
19
+ def init(
20
+ output: Path = typer.Option(
21
+ DEFAULT_OUTPUT_PATH,
22
+ "--output",
23
+ help="Path to write the generated notebook to (default: ./TableValidator.py).",
24
+ ),
25
+ mode: str = typer.Option(
26
+ "full",
27
+ "--mode",
28
+ help=(
29
+ "basic: only the check-group multiselect and table pickers. "
30
+ "full (default): also shows optional filter widgets (only/ "
31
+ "ignore columns, row filter, primary key). "
32
+ "schema: pre-selects only the Catalog & Schema check group."
33
+ ),
34
+ ),
35
+ ) -> None:
36
+ """Generate a Databricks notebook with dbutils widgets for source/
37
+ target catalog.schema.table selection and validation checks - fill in
38
+ the widgets and run the notebook, no code to write.
39
+
40
+ Import the generated file into a Databricks workspace via
41
+ Workspace -> Import, with format set to "Source" (or "File" -> upload
42
+ directly, depending on your workspace UI version) - Databricks
43
+ recognizes the "# Databricks notebook source" header and cell
44
+ (# COMMAND ----------) markers and opens it as a real notebook, not a
45
+ plain text file.
46
+ """
47
+ if mode not in VALID_MODES:
48
+ typer.secho(
49
+ f"Invalid --mode {mode!r} - must be one of {', '.join(VALID_MODES)}.",
50
+ fg=typer.colors.RED,
51
+ )
52
+ raise typer.Exit(code=1)
53
+
54
+ generate_notebook(output, mode)
55
+
56
+ typer.echo(f"Generated notebook: {output.resolve()}")
57
+ typer.echo(
58
+ "Import it into Databricks (Workspace -> Import, format 'Source'), "
59
+ "fill in the widgets, and Run All."
60
+ )
61
+
62
+
63
+ @app.command()
64
+ def info() -> None:
65
+ """Show what this tool does and how to use the generated notebook."""
66
+ typer.echo(
67
+ "\n"
68
+ "tablevalidator-databricks - Databricks notebook UI for table-validator\n"
69
+ "------------------------------------------------------------------------\n"
70
+ "Generates a Databricks notebook with dbutils widgets (dynamic "
71
+ "catalog/schema/table dropdowns sourced from Unity Catalog, plus a "
72
+ "validation-checks multiselect) as a wrapper around the "
73
+ "table-validator package's validate_tables() API - no Python code "
74
+ "to write, no separate credentials to configure (the notebook "
75
+ "reuses its own ambient Spark session, same as validate_tables() "
76
+ "itself).\n"
77
+ "\n"
78
+ "Workflow:\n"
79
+ "\n"
80
+ " 1. tablevalidator-databricks init\n"
81
+ " Writes a ready-to-use notebook (./TableValidator.py by "
82
+ "default). Use --output to write elsewhere, --mode to pick basic/"
83
+ "full/schema (see 'tablevalidator-databricks init --help').\n"
84
+ "\n"
85
+ " 2. Import the generated file into Databricks\n"
86
+ " Workspace -> Import, format 'Source'.\n"
87
+ "\n"
88
+ " 3. Fill in the widgets and Run All\n"
89
+ " Pick Source/Target Catalog, Schema, Table, and which checks "
90
+ "to run, then run the notebook top to bottom. Re-run the "
91
+ "'Create widgets' cell after changing a Catalog/Schema selection, "
92
+ "to repopulate the dropdowns below it (a Databricks widget "
93
+ "limitation, not a bug).\n"
94
+ "\n"
95
+ "This package contains no validation logic of its own - every "
96
+ "generated notebook calls directly into table-validator's own "
97
+ "validate_tables() engine, so results are identical to calling it "
98
+ "yourself in a code cell.\n"
99
+ )
File without changes
@@ -0,0 +1,39 @@
1
+ """
2
+ Renders the bundled widget_notebook.py.tmpl into a real Databricks
3
+ notebook file. No Jinja2 - the template has exactly two placeholders,
4
+ substituted via plain str.replace(), so a templating dependency isn't
5
+ worth adding for this.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.resources
11
+ from pathlib import Path
12
+
13
+ VALID_MODES = ("basic", "full", "schema")
14
+
15
+
16
+ def generate_notebook(output: Path, mode: str) -> None:
17
+ """Write a widget-driven Databricks notebook to `output`.
18
+
19
+ mode controls which optional widgets/pre-selected checks the notebook
20
+ starts with - see tablevalidator_databricks/cli/main.py's `init`
21
+ command docstring for what each mode means. The underlying comparison
22
+ logic is identical regardless of mode; only the widgets shown differ,
23
+ so all three modes render from the same template file rather than
24
+ three separate ones.
25
+ """
26
+ if mode not in VALID_MODES:
27
+ raise ValueError(f"mode must be one of {VALID_MODES}, got {mode!r}")
28
+
29
+ template = (
30
+ importlib.resources.files("tablevalidator_databricks.templates")
31
+ .joinpath("widget_notebook.py.tmpl")
32
+ .read_text(encoding="utf-8")
33
+ )
34
+ rendered = template.replace("{{MODE}}", mode).replace(
35
+ "{{SHOW_OPTIONAL_WIDGETS}}", "True" if mode == "full" else "False",
36
+ )
37
+
38
+ output.parent.mkdir(parents=True, exist_ok=True)
39
+ output.write_text(rendered, encoding="utf-8")
@@ -0,0 +1,178 @@
1
+ # Databricks notebook source
2
+ # MAGIC %md
3
+ # MAGIC # Table Validator
4
+ # MAGIC Generated by `tablevalidator-databricks init --mode {{MODE}}`.
5
+ # MAGIC
6
+ # MAGIC Fill in the widgets above (Source/Target Catalog, Schema, Table, and
7
+ # MAGIC which checks to run), then **Run All**. No code needs to be written or
8
+ # MAGIC edited - every cell below just reads the widgets and calls the
9
+ # MAGIC `table_validator` package's `validate_tables()` API.
10
+ # MAGIC
11
+ # MAGIC **Cascading dropdowns**: Databricks widgets don't auto-refresh when an
12
+ # MAGIC upstream selection changes. After picking a different Source/Target
13
+ # MAGIC Catalog, re-run the "Create widgets" cell below to repopulate the Schema
14
+ # MAGIC dropdown for that catalog; likewise after changing Schema, re-run it
15
+ # MAGIC again to repopulate Table. This is a real Databricks widget limitation,
16
+ # MAGIC not a bug in this notebook.
17
+
18
+ # COMMAND ----------
19
+
20
+ # MAGIC %pip install table-validator
21
+
22
+ # COMMAND ----------
23
+
24
+ dbutils.library.restartPython()
25
+
26
+ # COMMAND ----------
27
+
28
+ from table_validator import validate_tables
29
+ from table_validator.config.schema import ValidationType
30
+
31
+ MODE = "{{MODE}}"
32
+ SHOW_OPTIONAL_WIDGETS = {{SHOW_OPTIONAL_WIDGETS}}
33
+
34
+
35
+ def _list_catalogs():
36
+ return sorted(r["catalog"] for r in spark.sql("SHOW CATALOGS").collect())
37
+
38
+
39
+ def _list_schemas(catalog: str):
40
+ if not catalog:
41
+ return []
42
+ rows = spark.sql(f"SHOW SCHEMAS IN `{catalog}`").collect()
43
+ return sorted(r["databaseName"] for r in rows)
44
+
45
+
46
+ def _list_tables(catalog: str, schema: str):
47
+ if not catalog or not schema:
48
+ return []
49
+ rows = spark.sql(f"SHOW TABLES IN `{catalog}`.`{schema}`").collect()
50
+ return sorted(r["tableName"] for r in rows)
51
+
52
+
53
+ def _current_or_first(options, current):
54
+ """Keep a widget's existing selection across a re-run of this cell if
55
+ it's still valid for the (possibly new) upstream selection, otherwise
56
+ fall back to the first available option (or "" if there are none)."""
57
+ if current in options:
58
+ return current
59
+ return options[0] if options else ""
60
+
61
+ # COMMAND ----------
62
+
63
+ # MAGIC %md ### Create widgets
64
+ # MAGIC Re-run this cell after changing Source/Target Catalog or Schema, to
65
+ # MAGIC repopulate the dropdowns below them.
66
+
67
+ # COMMAND ----------
68
+
69
+ def _get_widget(name: str, default: str = "") -> str:
70
+ try:
71
+ return dbutils.widgets.get(name)
72
+ except Exception:
73
+ return default
74
+
75
+
76
+ catalogs = _list_catalogs()
77
+
78
+ source_catalog_current = _current_or_first(catalogs, _get_widget("source_catalog"))
79
+ target_catalog_current = _current_or_first(catalogs, _get_widget("target_catalog"))
80
+
81
+ source_schemas = _list_schemas(source_catalog_current)
82
+ target_schemas = _list_schemas(target_catalog_current)
83
+
84
+ source_schema_current = _current_or_first(source_schemas, _get_widget("source_schema"))
85
+ target_schema_current = _current_or_first(target_schemas, _get_widget("target_schema"))
86
+
87
+ source_tables = _list_tables(source_catalog_current, source_schema_current)
88
+ target_tables = _list_tables(target_catalog_current, target_schema_current)
89
+
90
+ source_table_current = _current_or_first(source_tables, _get_widget("source_table"))
91
+ target_table_current = _current_or_first(target_tables, _get_widget("target_table"))
92
+
93
+ dbutils.widgets.dropdown("source_catalog", source_catalog_current, catalogs or [""], "Source Catalog")
94
+ dbutils.widgets.dropdown("source_schema", source_schema_current, source_schemas or [""], "Source Schema")
95
+ dbutils.widgets.dropdown("source_table", source_table_current, source_tables or [""], "Source Table")
96
+
97
+ dbutils.widgets.dropdown("target_catalog", target_catalog_current, catalogs or [""], "Target Catalog")
98
+ dbutils.widgets.dropdown("target_schema", target_schema_current, target_schemas or [""], "Target Schema")
99
+ dbutils.widgets.dropdown("target_table", target_table_current, target_tables or [""], "Target Table")
100
+
101
+ print(
102
+ "Widgets created. If you just changed Source/Target Catalog or Schema, "
103
+ "re-run this cell once more to repopulate the dropdowns below it."
104
+ )
105
+
106
+ # COMMAND ----------
107
+
108
+ # MAGIC %md ### Validation checks
109
+
110
+ # COMMAND ----------
111
+
112
+ _default_checks = ["Catalog & Schema", "Column", "Row"] if MODE != "schema" else ["Catalog & Schema"]
113
+ dbutils.widgets.multiselect(
114
+ "checks", ",".join(_default_checks),
115
+ ["Catalog & Schema", "Column", "Row"], "Validation Checks",
116
+ )
117
+
118
+ if SHOW_OPTIONAL_WIDGETS:
119
+ dbutils.widgets.text("only_columns", "", "Only compare these columns (comma-separated, optional)")
120
+ dbutils.widgets.text("ignore_columns", "", "Skip these columns entirely (comma-separated, optional)")
121
+ dbutils.widgets.text("row_filter", "", "Row filter - SQL WHERE-fragment (optional)")
122
+ dbutils.widgets.text("primary_key", "", "Primary key column(s) (comma-separated, optional)")
123
+
124
+ # COMMAND ----------
125
+
126
+ # MAGIC %md ### Run validation
127
+
128
+ # COMMAND ----------
129
+
130
+ source = f"{dbutils.widgets.get('source_catalog')}.{dbutils.widgets.get('source_schema')}.{dbutils.widgets.get('source_table')}"
131
+ target = f"{dbutils.widgets.get('target_catalog')}.{dbutils.widgets.get('target_schema')}.{dbutils.widgets.get('target_table')}"
132
+
133
+ _CHECK_MAP = {
134
+ "Catalog & Schema": {ValidationType.CATALOG, ValidationType.SCHEMA},
135
+ "Column": {ValidationType.COLUMN},
136
+ "Row": {ValidationType.ROW},
137
+ }
138
+ selected_checks = [c for c in dbutils.widgets.get("checks").split(",") if c]
139
+ enabled_validations = set()
140
+ for check in selected_checks:
141
+ enabled_validations |= _CHECK_MAP.get(check, set())
142
+ if not enabled_validations:
143
+ enabled_validations = None # fall back to validate_tables()'s own "run everything" default
144
+
145
+ kwargs = {"enabled_validations": enabled_validations}
146
+
147
+ if SHOW_OPTIONAL_WIDGETS:
148
+ only_columns = [c.strip() for c in dbutils.widgets.get("only_columns").split(",") if c.strip()]
149
+ ignore_columns = [c.strip() for c in dbutils.widgets.get("ignore_columns").split(",") if c.strip()]
150
+ row_filter = dbutils.widgets.get("row_filter").strip()
151
+ primary_key = [c.strip() for c in dbutils.widgets.get("primary_key").split(",") if c.strip()]
152
+
153
+ if only_columns:
154
+ kwargs["only_columns"] = only_columns
155
+ if ignore_columns:
156
+ kwargs["ignore_columns"] = ignore_columns
157
+ if row_filter:
158
+ kwargs["row_filter"] = row_filter
159
+ if primary_key:
160
+ kwargs["primary_key"] = primary_key
161
+
162
+ result = validate_tables(source, target, **kwargs)
163
+
164
+ # COMMAND ----------
165
+
166
+ # MAGIC %md ### Results
167
+
168
+ # COMMAND ----------
169
+
170
+ print(result)
171
+ display(result.table_validation.to_dataframe())
172
+
173
+ # Other sheets, mirroring the Excel report's own tabs - uncomment as needed:
174
+ # display(result.column_validation.to_dataframe())
175
+ # display(result.data_mismatches.to_dataframe())
176
+ # display(result.row_hash_mismatches.to_dataframe())
177
+ # display(result.mismatch_categories.to_dataframe())
178
+ # display(result.suggestions.to_dataframe())
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: tablevalidator-databricks
3
+ Version: 0.1.0
4
+ Summary: Generates a widget-driven Databricks notebook UI on top of the table-validator package - no code required to run a comparison.
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: typer>=0.12
9
+ Requires-Dist: table-validator>=0.1.19
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=7.0; extra == "dev"
12
+ Requires-Dist: ruff>=0.4; extra == "dev"
13
+ Requires-Dist: black>=24.0; extra == "dev"
14
+
15
+ # tablevalidator-databricks
16
+
17
+ A widget-driven Databricks notebook UI for [`table-validator`](https://pypi.org/project/table-validator/)
18
+ - generates a notebook with `dbutils.widgets` for picking source/target
19
+ tables and validation checks, so you can run a comparison without writing
20
+ any Python.
21
+
22
+ This package contains **no validation logic of its own**. It only writes a
23
+ notebook file whose cells call directly into `table-validator`'s own
24
+ `validate_tables()` API - the exact same engine the CLI (`tablevalidator`)
25
+ and the notebook-native Python API already use.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install tablevalidator-databricks
31
+ ```
32
+
33
+ Installing this package also installs `table-validator` (a dependency), so
34
+ there's nothing else to install separately.
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ tablevalidator-databricks init
40
+ ```
41
+
42
+ Writes `./TableValidator.py` - a ready-to-use Databricks notebook. Options:
43
+
44
+ ```bash
45
+ tablevalidator-databricks init --output MyValidation.py --mode full
46
+ ```
47
+
48
+ - `--mode basic` - only the check-group multiselect and table pickers.
49
+ - `--mode full` (default) - also shows optional filter widgets (only/
50
+ ignore columns, row filter, primary key).
51
+ - `--mode schema` - pre-selects only the "Catalog & Schema" check group,
52
+ for a lightweight schema-shape-only comparison.
53
+
54
+ Then, in your Databricks workspace: **Workspace -> Import**, format
55
+ "Source", and select the generated file. Databricks recognizes the
56
+ `# Databricks notebook source` header and `# COMMAND ----------` cell
57
+ markers and opens it as a real notebook.
58
+
59
+ ## What the generated notebook does
60
+
61
+ 1. Installs `table-validator` (`%pip install table-validator`).
62
+ 2. Queries Unity Catalog (`SHOW CATALOGS` / `SHOW SCHEMAS IN ...` /
63
+ `SHOW TABLES IN ...` via the notebook's own ambient Spark session) to
64
+ populate Source/Target Catalog, Schema, and Table dropdowns.
65
+ 3. Shows a "Validation Checks" multiselect (Catalog & Schema / Column /
66
+ Row - the three independently-selectable check groups the engine
67
+ actually supports) plus, in `full` mode, optional text widgets for
68
+ column filtering, a row filter, and a primary key.
69
+ 4. On run, reads every widget and calls
70
+ `validate_tables(source, target, enabled_validations=..., ...)`,
71
+ then prints a summary and displays the Table Validation sheet.
72
+
73
+ **Cascading dropdowns**: Databricks widgets don't auto-refresh when an
74
+ upstream selection changes. After changing Source/Target Catalog or
75
+ Schema, re-run the notebook's "Create widgets" cell once to repopulate the
76
+ dropdowns below it before running the rest of the notebook. This is a
77
+ Databricks widget limitation, not a bug in the generated notebook.
78
+
79
+ ## Why a separate package
80
+
81
+ `table-validator` stays focused on the actual comparison engine (CLI +
82
+ notebook API + report generation). This package is purely a Databricks-
83
+ specific UI layer on top of it - keeping the two independently versioned
84
+ and installable means CLI-only or code-only users never need Databricks-
85
+ specific tooling pulled in, and this package can add more Databricks UX
86
+ (e.g. richer widgets) without touching the core engine at all.
87
+
88
+ ## Development
89
+
90
+ ```bash
91
+ pip install -e ".[dev]"
92
+ pytest
93
+ ```
94
+
95
+ ## License
96
+
97
+ MIT - see the `table-validator` repository's [LICENSE](../table_validator/LICENSE).
@@ -0,0 +1,11 @@
1
+ tablevalidator_databricks/__init__.py,sha256=qhtaaSY_pHgTu5JxvXVoSAMfVC1R2I5OcPcyzLQISQo,560
2
+ tablevalidator_databricks/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ tablevalidator_databricks/cli/main.py,sha256=p4ex05jnieabbg1ANuXOCKKJd7-zlbs-PUz5fXD7KAU,3897
4
+ tablevalidator_databricks/generator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ tablevalidator_databricks/generator/generator.py,sha256=Gn9ziozDmYvK7TnctnVlqu2_Oe8R4DsdN9NQYJcAzMg,1418
6
+ tablevalidator_databricks/templates/widget_notebook.py.tmpl,sha256=k_jGdIZN6tqHPsk1tOLK-E7Qv-_kY-YGA4c90L29m-U,6759
7
+ tablevalidator_databricks-0.1.0.dist-info/METADATA,sha256=2eVYYB4qHAUpFquB2sQyHMFLB7Z-MhTWjuRlsLZwmzU,3713
8
+ tablevalidator_databricks-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ tablevalidator_databricks-0.1.0.dist-info/entry_points.txt,sha256=PzigbELsTjFQ9wJIge3nI3sBQWLsptchG-jyovFFreI,85
10
+ tablevalidator_databricks-0.1.0.dist-info/top_level.txt,sha256=LgxXkFPKeoTSwNXHPQLzWd4x4qDNL86KDxqbCCIb-b4,26
11
+ tablevalidator_databricks-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tablevalidator-databricks = tablevalidator_databricks.cli.main:app
@@ -0,0 +1 @@
1
+ tablevalidator_databricks