tablevalidator-databricks 0.1.7__tar.gz → 0.1.8__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 (22) hide show
  1. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/PKG-INFO +1 -1
  2. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/templates/widget_notebook.py.tmpl +40 -10
  3. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/PKG-INFO +1 -1
  4. tablevalidator_databricks-0.1.8/tablevalidator_databricks.egg-info/scm_version.json +8 -0
  5. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tests/test_generated_notebook_execution.py +73 -0
  6. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tests/test_generator.py +21 -3
  7. tablevalidator_databricks-0.1.7/tablevalidator_databricks.egg-info/scm_version.json +0 -8
  8. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/README.md +0 -0
  9. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/pyproject.toml +0 -0
  10. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/setup.cfg +0 -0
  11. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/__init__.py +0 -0
  12. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/__init__.py +0 -0
  13. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/main.py +0 -0
  14. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/__init__.py +0 -0
  15. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/generator.py +0 -0
  16. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/SOURCES.txt +0 -0
  17. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/dependency_links.txt +0 -0
  18. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/entry_points.txt +0 -0
  19. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/requires.txt +0 -0
  20. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/scm_file_list.json +0 -0
  21. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/top_level.txt +0 -0
  22. {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablevalidator-databricks
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Generates a widget-driven Databricks notebook UI on top of the table-validator package - no code required to run a comparison.
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -42,18 +42,25 @@
42
42
  # COMMAND ----------
43
43
 
44
44
  # MAGIC %md ### Install the validation engine
45
- # MAGIC Note the `>=` version floor: `validate_tables()` (the API every cell
46
- # MAGIC below calls) only exists in `table-validator` {{MIN_CORE_VERSION}} and
47
- # MAGIC later. **Do not pin this to a lower version** - in particular, do not
48
- # MAGIC confuse it with the `tablevalidator-databricks` version that generated
49
- # MAGIC this notebook; the two packages are versioned completely independently,
50
- # MAGIC and pinning the engine to the generator's version number installs an
51
- # MAGIC engine far too old, failing with
45
+ # MAGIC This always pulls the **latest** `table-validator`. The flags matter:
46
+ # MAGIC `--upgrade` replaces an engine that is already installed on the cluster
47
+ # MAGIC (without it pip just prints "Requirement already satisfied" and keeps
48
+ # MAGIC the old one), and `--no-cache-dir` avoids a stale cached wheel being
49
+ # MAGIC reused. The next cell prints the version actually loaded, so you can
50
+ # MAGIC always confirm what you are running.
51
+ # MAGIC
52
+ # MAGIC The `>=` floor is a minimum, not a pin: `validate_tables()` (the API
53
+ # MAGIC every cell below calls) only exists in `table-validator`
54
+ # MAGIC {{MIN_CORE_VERSION}} and later. **Do not pin this to a lower version** -
55
+ # MAGIC in particular, do not confuse it with the `tablevalidator-databricks`
56
+ # MAGIC version that generated this notebook; the two packages are versioned
57
+ # MAGIC completely independently, and pinning the engine to the generator's
58
+ # MAGIC version number installs an engine far too old, failing with
52
59
  # MAGIC `ImportError: cannot import name 'validate_tables'`.
53
60
 
54
61
  # COMMAND ----------
55
62
 
56
- # MAGIC %pip install --upgrade "table-validator>={{MIN_CORE_VERSION}}"
63
+ # MAGIC %pip install --upgrade --upgrade-strategy eager --no-cache-dir "table-validator>={{MIN_CORE_VERSION}}"
57
64
 
58
65
  # COMMAND ----------
59
66
 
@@ -207,11 +214,25 @@ def _build_cascading_widgets(prefix: str, label_prefix: str) -> None:
207
214
  table_choices = [_ALL_TABLES] + _list_tables(catalog_current, schema_current)
208
215
  table_current = _current_or_first_real(table_choices, _effective_value(f"{prefix}_table"), _ALL_TABLES)
209
216
 
217
+ # Only the three pickers here - the matching "(manual override)" text
218
+ # boxes are created later, by _build_override_widgets(), so that the
219
+ # essential Catalog -> Schema -> Table selection isn't broken up by
220
+ # advanced fields. Databricks lays widgets out in creation order, so
221
+ # creation order IS the on-screen order.
210
222
  dbutils.widgets.dropdown(f"{prefix}_catalog", catalog_current, catalogs or [""], f"{label_prefix} Catalog")
211
- dbutils.widgets.text(f"{prefix}_catalog_override", "", f"{label_prefix} Catalog (manual override)")
212
223
  dbutils.widgets.dropdown(f"{prefix}_schema", schema_current, schema_choices, f"{label_prefix} Schema")
213
- dbutils.widgets.text(f"{prefix}_schema_override", "", f"{label_prefix} Schema (manual override)")
214
224
  dbutils.widgets.dropdown(f"{prefix}_table", table_current, table_choices, f"{label_prefix} Table")
225
+
226
+
227
+ def _build_override_widgets(prefix: str, label_prefix: str) -> None:
228
+ """The "(manual override)" text box for each of one side's pickers.
229
+
230
+ Created after every dropdown and check widget, since these are an
231
+ escape hatch for a name the dropdown doesn't list - most runs leave
232
+ all six blank, so they belong at the end rather than interleaved with
233
+ the pickers people actually use."""
234
+ dbutils.widgets.text(f"{prefix}_catalog_override", "", f"{label_prefix} Catalog (manual override)")
235
+ dbutils.widgets.text(f"{prefix}_schema_override", "", f"{label_prefix} Schema (manual override)")
215
236
  dbutils.widgets.text(f"{prefix}_table_override", "", f"{label_prefix} Table (manual override)")
216
237
 
217
238
 
@@ -249,10 +270,19 @@ def _build_check_widgets() -> None:
249
270
  # and in a predictable order - Databricks lays widgets out in creation
250
271
  # order, so splitting these across cells made them interleave oddly and
251
272
  # made later ones seem to "appear" partway through a Run All.
273
+ #
274
+ # Order is deliberate, most-used first:
275
+ # 1. Source Catalog / Schema / Table - what you're comparing
276
+ # 2. Target Catalog / Schema / Table
277
+ # 3. Which checks to run - the three Yes/No dropdowns
278
+ # 4. Optional filters - primary key, columns, row filter
279
+ # 5. Manual overrides - rarely needed escape hatch
252
280
  catalogs = _list_catalogs()
253
281
  _build_cascading_widgets("source", "Source")
254
282
  _build_cascading_widgets("target", "Target")
255
283
  _build_check_widgets()
284
+ _build_override_widgets("source", "Source")
285
+ _build_override_widgets("target", "Target")
256
286
 
257
287
  print(
258
288
  "Widgets refreshed for the CURRENT Catalog/Schema selection.\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablevalidator-databricks
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Generates a widget-driven Databricks notebook UI on top of the table-validator package - no code required to run a comparison.
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.1.8",
3
+ "distance": 0,
4
+ "node": "g0e12068",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-11"
8
+ }
@@ -421,3 +421,76 @@ def test_catalog_wide_sweep_raises_clear_error(notebook_path):
421
421
  spark=spark,
422
422
  validate_tables_mock=MagicMock(),
423
423
  )
424
+
425
+
426
+ # ---------------------------------------------------------------------------
427
+ # Widget ORDER. Databricks lays widgets out in creation order, so creation
428
+ # order is the on-screen order. Essential pickers must come first and the
429
+ # rarely-needed manual overrides last.
430
+ # ---------------------------------------------------------------------------
431
+ def _widget_creation_order(path: Path) -> list:
432
+ """Run the notebook and record the order widgets are created in."""
433
+ order = []
434
+
435
+ class _OrderRecordingWidgets(_FakeWidgets):
436
+ def text(self, name, default, label):
437
+ order.append(name)
438
+ super().text(name, default, label)
439
+
440
+ def dropdown(self, name, default, choices, label):
441
+ order.append(name)
442
+ super().dropdown(name, default, choices, label)
443
+
444
+ def multiselect(self, name, default, choices, label):
445
+ order.append(name)
446
+ super().multiselect(name, default, choices, label)
447
+
448
+ spark = _fake_spark(
449
+ catalogs=["main"], schemas_by_catalog={"main": ["sales"]},
450
+ tables_by_schema={("main", "sales"): ["orders"]},
451
+ )
452
+ code = _code_from_notebook(path)
453
+
454
+ fake_dbutils = MagicMock()
455
+ fake_dbutils.widgets = _OrderRecordingWidgets()
456
+ fake_dbutils.library.restartPython = MagicMock()
457
+
458
+ import table_validator
459
+ import table_validator.notebook as nb_mod
460
+ stub = MagicMock()
461
+ table_validator.validate_tables = stub
462
+ nb_mod.validate_tables = stub
463
+
464
+ exec(compile(code, str(path), "exec"), {
465
+ "dbutils": fake_dbutils, "spark": spark,
466
+ "display": MagicMock(), "__name__": "__main__",
467
+ })
468
+ return order
469
+
470
+
471
+ def test_source_and_target_pickers_come_first(notebook_path):
472
+ """What you're comparing is the first thing you should see - the six
473
+ Catalog/Schema/Table pickers, Source then Target, before anything
474
+ else."""
475
+ order = _widget_creation_order(notebook_path)
476
+
477
+ assert order[:6] == [
478
+ "source_catalog", "source_schema", "source_table",
479
+ "target_catalog", "target_schema", "target_table",
480
+ ]
481
+
482
+
483
+ def test_manual_overrides_come_last(notebook_path):
484
+ """The override boxes are an escape hatch most runs leave blank, so
485
+ they belong at the end rather than interleaved with the pickers."""
486
+ order = _widget_creation_order(notebook_path)
487
+
488
+ assert all("_override" in name for name in order[-6:])
489
+
490
+
491
+ def test_checks_then_filters_sit_between_pickers_and_overrides(notebook_path):
492
+ order = _widget_creation_order(notebook_path)
493
+ middle = order[6:-6]
494
+
495
+ assert middle[:3] == ["check_catalog_schema", "check_column", "check_row"]
496
+ assert middle[3:] == ["primary_key", "only_columns", "ignore_columns", "row_filter"]
@@ -103,10 +103,11 @@ def test_generated_notebook_pins_a_core_version_floor(tmp_path: Path) -> None:
103
103
  generate_notebook(output, "full")
104
104
  content = output.read_text(encoding="utf-8")
105
105
 
106
- assert 'pip install --upgrade "table-validator>=' in content
106
+ assert "pip install --upgrade" in content
107
+ assert '"table-validator>=' in content
107
108
  # The floor must be new enough to actually contain validate_tables().
108
109
  match = re.search(
109
- r'pip install --upgrade "table-validator>=([0-9]+)\.([0-9]+)\.([0-9]+)"', content
110
+ r'"table-validator>=([0-9]+)\.([0-9]+)\.([0-9]+)"', content
110
111
  )
111
112
  assert match, "no parseable version floor in the %pip install line"
112
113
  major, minor, patch = (int(g) for g in match.groups())
@@ -144,7 +145,8 @@ def test_pip_install_line_forces_an_upgrade(tmp_path: Path) -> None:
144
145
  generate_notebook(output, "full")
145
146
  content = output.read_text(encoding="utf-8")
146
147
 
147
- assert 'pip install --upgrade "table-validator>=' in content
148
+ assert "pip install --upgrade" in content
149
+ assert '"table-validator>=' in content
148
150
 
149
151
 
150
152
  def test_notebook_reports_the_loaded_engine_version(tmp_path: Path) -> None:
@@ -170,3 +172,19 @@ def test_core_version_floor_includes_the_arrow_fix(tmp_path: Path) -> None:
170
172
  match = re.search(r'table-validator>=([0-9]+)\.([0-9]+)\.([0-9]+)"', content)
171
173
  assert match
172
174
  assert tuple(int(g) for g in match.groups()) >= (0, 1, 22)
175
+
176
+
177
+ def test_install_line_always_fetches_the_latest_engine(tmp_path: Path) -> None:
178
+ """The install must actually land the newest release. --upgrade
179
+ replaces an engine already present on the cluster (without it pip
180
+ just reports "Requirement already satisfied"), and --no-cache-dir
181
+ stops a stale cached wheel being reused."""
182
+ output = tmp_path / "TableValidator.py"
183
+ generate_notebook(output, "full")
184
+ content = output.read_text(encoding="utf-8")
185
+
186
+ install_line = next(
187
+ line for line in content.splitlines() if "pip install" in line and "table-validator>=" in line
188
+ )
189
+ assert "--upgrade" in install_line
190
+ assert "--no-cache-dir" in install_line
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.1.7",
3
- "distance": 0,
4
- "node": "ga3dbc87",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-10"
8
- }