tablevalidator-databricks 0.1.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 (20) hide show
  1. tablevalidator_databricks-0.1.0/PKG-INFO +97 -0
  2. tablevalidator_databricks-0.1.0/README.md +83 -0
  3. tablevalidator_databricks-0.1.0/pyproject.toml +50 -0
  4. tablevalidator_databricks-0.1.0/setup.cfg +4 -0
  5. tablevalidator_databricks-0.1.0/tablevalidator_databricks/__init__.py +15 -0
  6. tablevalidator_databricks-0.1.0/tablevalidator_databricks/cli/__init__.py +0 -0
  7. tablevalidator_databricks-0.1.0/tablevalidator_databricks/cli/main.py +99 -0
  8. tablevalidator_databricks-0.1.0/tablevalidator_databricks/generator/__init__.py +0 -0
  9. tablevalidator_databricks-0.1.0/tablevalidator_databricks/generator/generator.py +39 -0
  10. tablevalidator_databricks-0.1.0/tablevalidator_databricks/templates/widget_notebook.py.tmpl +178 -0
  11. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/PKG-INFO +97 -0
  12. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/SOURCES.txt +18 -0
  13. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/dependency_links.txt +1 -0
  14. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/entry_points.txt +2 -0
  15. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/requires.txt +7 -0
  16. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/scm_file_list.json +14 -0
  17. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/scm_version.json +8 -0
  18. tablevalidator_databricks-0.1.0/tablevalidator_databricks.egg-info/top_level.txt +1 -0
  19. tablevalidator_databricks-0.1.0/tests/test_cli.py +56 -0
  20. tablevalidator_databricks-0.1.0/tests/test_generator.py +75 -0
@@ -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,83 @@
1
+ # tablevalidator-databricks
2
+
3
+ A widget-driven Databricks notebook UI for [`table-validator`](https://pypi.org/project/table-validator/)
4
+ - generates a notebook with `dbutils.widgets` for picking source/target
5
+ tables and validation checks, so you can run a comparison without writing
6
+ any Python.
7
+
8
+ This package contains **no validation logic of its own**. It only writes a
9
+ notebook file whose cells call directly into `table-validator`'s own
10
+ `validate_tables()` API - the exact same engine the CLI (`tablevalidator`)
11
+ and the notebook-native Python API already use.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install tablevalidator-databricks
17
+ ```
18
+
19
+ Installing this package also installs `table-validator` (a dependency), so
20
+ there's nothing else to install separately.
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ tablevalidator-databricks init
26
+ ```
27
+
28
+ Writes `./TableValidator.py` - a ready-to-use Databricks notebook. Options:
29
+
30
+ ```bash
31
+ tablevalidator-databricks init --output MyValidation.py --mode full
32
+ ```
33
+
34
+ - `--mode basic` - only the check-group multiselect and table pickers.
35
+ - `--mode full` (default) - also shows optional filter widgets (only/
36
+ ignore columns, row filter, primary key).
37
+ - `--mode schema` - pre-selects only the "Catalog & Schema" check group,
38
+ for a lightweight schema-shape-only comparison.
39
+
40
+ Then, in your Databricks workspace: **Workspace -> Import**, format
41
+ "Source", and select the generated file. Databricks recognizes the
42
+ `# Databricks notebook source` header and `# COMMAND ----------` cell
43
+ markers and opens it as a real notebook.
44
+
45
+ ## What the generated notebook does
46
+
47
+ 1. Installs `table-validator` (`%pip install table-validator`).
48
+ 2. Queries Unity Catalog (`SHOW CATALOGS` / `SHOW SCHEMAS IN ...` /
49
+ `SHOW TABLES IN ...` via the notebook's own ambient Spark session) to
50
+ populate Source/Target Catalog, Schema, and Table dropdowns.
51
+ 3. Shows a "Validation Checks" multiselect (Catalog & Schema / Column /
52
+ Row - the three independently-selectable check groups the engine
53
+ actually supports) plus, in `full` mode, optional text widgets for
54
+ column filtering, a row filter, and a primary key.
55
+ 4. On run, reads every widget and calls
56
+ `validate_tables(source, target, enabled_validations=..., ...)`,
57
+ then prints a summary and displays the Table Validation sheet.
58
+
59
+ **Cascading dropdowns**: Databricks widgets don't auto-refresh when an
60
+ upstream selection changes. After changing Source/Target Catalog or
61
+ Schema, re-run the notebook's "Create widgets" cell once to repopulate the
62
+ dropdowns below it before running the rest of the notebook. This is a
63
+ Databricks widget limitation, not a bug in the generated notebook.
64
+
65
+ ## Why a separate package
66
+
67
+ `table-validator` stays focused on the actual comparison engine (CLI +
68
+ notebook API + report generation). This package is purely a Databricks-
69
+ specific UI layer on top of it - keeping the two independently versioned
70
+ and installable means CLI-only or code-only users never need Databricks-
71
+ specific tooling pulled in, and this package can add more Databricks UX
72
+ (e.g. richer widgets) without touching the core engine at all.
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ pip install -e ".[dev]"
78
+ pytest
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT - see the `table-validator` repository's [LICENSE](../table_validator/LICENSE).
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "setuptools_scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tablevalidator-databricks"
7
+ dynamic = ["version"]
8
+ description = "Generates a widget-driven Databricks notebook UI on top of the table-validator package - no code required to run a comparison."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ dependencies = [
13
+ "typer>=0.12",
14
+ "table-validator>=0.1.19",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ dev = [
19
+ "pytest>=7.0",
20
+ "ruff>=0.4",
21
+ "black>=24.0",
22
+ ]
23
+
24
+ [project.scripts]
25
+ tablevalidator-databricks = "tablevalidator_databricks.cli.main:app"
26
+
27
+ [tool.setuptools.packages.find]
28
+ include = ["tablevalidator_databricks", "tablevalidator_databricks.*"]
29
+
30
+ [tool.setuptools.package-data]
31
+ tablevalidator_databricks = ["templates/*.tmpl"]
32
+
33
+ [tool.setuptools_scm]
34
+ root = ".."
35
+ # Distinct tag prefix ("databricks-vX.Y.Z") from table-validator's own
36
+ # "vX.Y.Z" tags (table_validator/pyproject.toml's tag_regex) - the two
37
+ # packages are versioned and released completely independently, and this
38
+ # package's own publish workflow only triggers on this tag pattern (see
39
+ # .github/workflows/publish-databricks.yml). Do not reuse table-validator's
40
+ # tag_regex here - a shared pattern would make both packages' publish
41
+ # workflows fire off the same tag.
42
+ #
43
+ # git_describe_command scopes `git describe` itself to only this package's
44
+ # tag pattern (via --match) - without this, `git describe` finds the
45
+ # repo's overall nearest tag (e.g. table-validator's own "v0.1.19") and
46
+ # setuptools_scm >=8 raises ValueError rather than silently falling back
47
+ # to fallback_version when that tag doesn't match tag_regex.
48
+ git_describe_command = "git describe --dirty --tags --long --match 'databricks-v*.*.*'"
49
+ tag_regex = "^databricks-v(?P<version>\\d+\\.\\d+\\.\\d+)$"
50
+ fallback_version = "0.0.0"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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__"]
@@ -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
+ )
@@ -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,18 @@
1
+ README.md
2
+ pyproject.toml
3
+ tablevalidator_databricks/__init__.py
4
+ tablevalidator_databricks.egg-info/PKG-INFO
5
+ tablevalidator_databricks.egg-info/SOURCES.txt
6
+ tablevalidator_databricks.egg-info/dependency_links.txt
7
+ tablevalidator_databricks.egg-info/entry_points.txt
8
+ tablevalidator_databricks.egg-info/requires.txt
9
+ tablevalidator_databricks.egg-info/scm_file_list.json
10
+ tablevalidator_databricks.egg-info/scm_version.json
11
+ tablevalidator_databricks.egg-info/top_level.txt
12
+ tablevalidator_databricks/cli/__init__.py
13
+ tablevalidator_databricks/cli/main.py
14
+ tablevalidator_databricks/generator/__init__.py
15
+ tablevalidator_databricks/generator/generator.py
16
+ tablevalidator_databricks/templates/widget_notebook.py.tmpl
17
+ tests/test_cli.py
18
+ tests/test_generator.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tablevalidator-databricks = tablevalidator_databricks.cli.main:app
@@ -0,0 +1,7 @@
1
+ typer>=0.12
2
+ table-validator>=0.1.19
3
+
4
+ [dev]
5
+ pytest>=7.0
6
+ ruff>=0.4
7
+ black>=24.0
@@ -0,0 +1,14 @@
1
+ {
2
+ "files": [
3
+ "README.md",
4
+ "pyproject.toml",
5
+ "tablevalidator_databricks/__init__.py",
6
+ "tablevalidator_databricks/cli/__init__.py",
7
+ "tablevalidator_databricks/cli/main.py",
8
+ "tablevalidator_databricks/generator/__init__.py",
9
+ "tablevalidator_databricks/generator/generator.py",
10
+ "tablevalidator_databricks/templates/widget_notebook.py.tmpl",
11
+ "tests/test_cli.py",
12
+ "tests/test_generator.py"
13
+ ]
14
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.1.0",
3
+ "distance": 0,
4
+ "node": "g9124e37",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-08"
8
+ }
@@ -0,0 +1 @@
1
+ tablevalidator_databricks
@@ -0,0 +1,56 @@
1
+ """Tests for the tablevalidator-databricks CLI (cli/main.py)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from typer.testing import CliRunner
8
+
9
+ from tablevalidator_databricks.cli.main import app
10
+
11
+ runner = CliRunner()
12
+
13
+
14
+ def test_app_is_importable() -> None:
15
+ assert app is not None
16
+
17
+
18
+ def test_help_lists_init_command() -> None:
19
+ result = runner.invoke(app, ["--help"])
20
+ assert result.exit_code == 0
21
+ assert "init" in result.output
22
+
23
+
24
+ def test_init_writes_notebook_to_default_path(tmp_path: Path) -> None:
25
+ output = tmp_path / "TableValidator.py"
26
+
27
+ result = runner.invoke(app, ["init", "--output", str(output)])
28
+
29
+ assert result.exit_code == 0
30
+ assert output.exists()
31
+ assert "Generated notebook" in result.output
32
+
33
+
34
+ def test_init_respects_mode_flag(tmp_path: Path) -> None:
35
+ output = tmp_path / "basic.py"
36
+
37
+ result = runner.invoke(app, ["init", "--output", str(output), "--mode", "basic"])
38
+
39
+ assert result.exit_code == 0
40
+ content = output.read_text(encoding="utf-8")
41
+ assert 'MODE = "basic"' in content
42
+
43
+
44
+ def test_init_rejects_invalid_mode(tmp_path: Path) -> None:
45
+ output = tmp_path / "x.py"
46
+
47
+ result = runner.invoke(app, ["init", "--output", str(output), "--mode", "bogus"])
48
+
49
+ assert result.exit_code == 1
50
+ assert not output.exists()
51
+
52
+
53
+ def test_info_command_runs() -> None:
54
+ result = runner.invoke(app, ["info"])
55
+ assert result.exit_code == 0
56
+ assert "tablevalidator-databricks" in result.output
@@ -0,0 +1,75 @@
1
+ """Tests for generator.py's generate_notebook() - pure file-writing logic,
2
+ no Databricks/Spark involved."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ from tablevalidator_databricks.generator.generator import generate_notebook
11
+
12
+
13
+ def test_generate_notebook_writes_a_databricks_source_notebook(tmp_path: Path) -> None:
14
+ output = tmp_path / "TableValidator.py"
15
+
16
+ generate_notebook(output, "full")
17
+
18
+ assert output.exists()
19
+ content = output.read_text(encoding="utf-8")
20
+ assert content.startswith("# Databricks notebook source")
21
+ assert "# COMMAND ----------" in content
22
+ assert "from table_validator import validate_tables" in content
23
+
24
+
25
+ def test_generate_notebook_full_mode_shows_optional_widgets(tmp_path: Path) -> None:
26
+ output = tmp_path / "full.py"
27
+ generate_notebook(output, "full")
28
+ content = output.read_text(encoding="utf-8")
29
+
30
+ assert 'MODE = "full"' in content
31
+ assert "SHOW_OPTIONAL_WIDGETS = True" in content
32
+ assert 'dbutils.widgets.text("row_filter"' in content
33
+
34
+
35
+ def test_generate_notebook_basic_mode_hides_optional_widgets(tmp_path: Path) -> None:
36
+ output = tmp_path / "basic.py"
37
+ generate_notebook(output, "basic")
38
+ content = output.read_text(encoding="utf-8")
39
+
40
+ assert 'MODE = "basic"' in content
41
+ assert "SHOW_OPTIONAL_WIDGETS = False" in content
42
+
43
+
44
+ def test_generate_notebook_schema_mode_marker_present(tmp_path: Path) -> None:
45
+ output = tmp_path / "schema.py"
46
+ generate_notebook(output, "schema")
47
+ content = output.read_text(encoding="utf-8")
48
+
49
+ assert 'MODE = "schema"' in content
50
+ assert "SHOW_OPTIONAL_WIDGETS = False" in content
51
+
52
+
53
+ def test_generate_notebook_rejects_invalid_mode(tmp_path: Path) -> None:
54
+ with pytest.raises(ValueError, match="mode must be one of"):
55
+ generate_notebook(tmp_path / "x.py", "bogus")
56
+
57
+
58
+ def test_generate_notebook_creates_parent_directories(tmp_path: Path) -> None:
59
+ output = tmp_path / "nested" / "dir" / "TableValidator.py"
60
+
61
+ generate_notebook(output, "full")
62
+
63
+ assert output.exists()
64
+
65
+
66
+ def test_generate_notebook_no_leftover_placeholders(tmp_path: Path) -> None:
67
+ """Regression guard: every {{...}} placeholder in the template must be
68
+ substituted - a leftover placeholder would silently ship broken Python
69
+ to the user's notebook."""
70
+ output = tmp_path / "TableValidator.py"
71
+ generate_notebook(output, "full")
72
+ content = output.read_text(encoding="utf-8")
73
+
74
+ assert "{{" not in content
75
+ assert "}}" not in content