tablevalidator-databricks 0.1.6__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.6 → tablevalidator_databricks-0.1.8}/PKG-INFO +2 -2
  2. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/pyproject.toml +6 -1
  3. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/templates/widget_notebook.py.tmpl +69 -10
  4. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/PKG-INFO +2 -2
  5. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/requires.txt +1 -1
  6. tablevalidator_databricks-0.1.8/tablevalidator_databricks.egg-info/scm_version.json +8 -0
  7. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tests/test_generated_notebook_execution.py +73 -0
  8. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tests/test_generator.py +59 -2
  9. tablevalidator_databricks-0.1.6/tablevalidator_databricks.egg-info/scm_version.json +0 -8
  10. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/README.md +0 -0
  11. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/setup.cfg +0 -0
  12. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/__init__.py +0 -0
  13. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/__init__.py +0 -0
  14. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/main.py +0 -0
  15. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/__init__.py +0 -0
  16. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/generator.py +0 -0
  17. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/SOURCES.txt +0 -0
  18. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/dependency_links.txt +0 -0
  19. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/entry_points.txt +0 -0
  20. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/scm_file_list.json +0 -0
  21. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/top_level.txt +0 -0
  22. {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tests/test_cli.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablevalidator-databricks
3
- Version: 0.1.6
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: typer>=0.12
9
- Requires-Dist: table-validator>=0.1.19
9
+ Requires-Dist: table-validator>=0.1.22
10
10
  Provides-Extra: dev
11
11
  Requires-Dist: pytest>=7.0; extra == "dev"
12
12
  Requires-Dist: ruff>=0.4; extra == "dev"
@@ -11,7 +11,12 @@ requires-python = ">=3.9"
11
11
  license = { text = "MIT" }
12
12
  dependencies = [
13
13
  "typer>=0.12",
14
- "table-validator>=0.1.19",
14
+ # 0.1.22 is the first release whose result sheets survive Arrow
15
+ # conversion, which is what Databricks' display() uses - older engines
16
+ # fail with "ArrowTypeError: Expected bytes, got a 'int' object" on a
17
+ # column mixing row counts with blanks. The generated notebook's
18
+ # %pip install floor is read from this line.
19
+ "table-validator>=0.1.22",
15
20
  ]
16
21
 
17
22
  [project.optional-dependencies]
@@ -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 "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
 
@@ -61,12 +68,41 @@ dbutils.library.restartPython()
61
68
 
62
69
  # COMMAND ----------
63
70
 
71
+ import table_validator
64
72
  from table_validator import validate_tables
65
73
  from table_validator.config.schema import ValidationType
66
74
 
67
75
  MODE = "{{MODE}}"
68
76
  SHOW_OPTIONAL_WIDGETS = {{SHOW_OPTIONAL_WIDGETS}}
69
77
 
78
+ # Print what is actually loaded. A %pip install without --upgrade silently
79
+ # keeps an older already-installed engine ("Requirement already
80
+ # satisfied"), so the version that matters is the one imported here, not
81
+ # the one the install cell asked for.
82
+ _MIN_CORE = "{{MIN_CORE_VERSION}}"
83
+ print(f"table-validator engine: {table_validator.__version__} (need >= {_MIN_CORE})")
84
+
85
+
86
+ def _version_tuple(text):
87
+ parts = []
88
+ for chunk in str(text).split(".")[:3]:
89
+ digits = "".join(c for c in chunk if c.isdigit())
90
+ parts.append(int(digits) if digits else 0)
91
+ while len(parts) < 3:
92
+ parts.append(0)
93
+ return tuple(parts)
94
+
95
+
96
+ if _version_tuple(table_validator.__version__) < _version_tuple(_MIN_CORE):
97
+ print(
98
+ f"\n WARNING: this engine ({table_validator.__version__}) is older than "
99
+ f"{_MIN_CORE}, which this notebook expects.\n"
100
+ " You may hit errors such as 'ArrowTypeError: Expected bytes, got a "
101
+ "'int' object' when displaying results.\n"
102
+ " Fix: run the install cell above with --upgrade, then re-run "
103
+ "dbutils.library.restartPython() and start again."
104
+ )
105
+
70
106
 
71
107
  def _list_catalogs():
72
108
  return sorted(r["catalog"] for r in spark.sql("SHOW CATALOGS").collect())
@@ -178,11 +214,25 @@ def _build_cascading_widgets(prefix: str, label_prefix: str) -> None:
178
214
  table_choices = [_ALL_TABLES] + _list_tables(catalog_current, schema_current)
179
215
  table_current = _current_or_first_real(table_choices, _effective_value(f"{prefix}_table"), _ALL_TABLES)
180
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.
181
222
  dbutils.widgets.dropdown(f"{prefix}_catalog", catalog_current, catalogs or [""], f"{label_prefix} Catalog")
182
- dbutils.widgets.text(f"{prefix}_catalog_override", "", f"{label_prefix} Catalog (manual override)")
183
223
  dbutils.widgets.dropdown(f"{prefix}_schema", schema_current, schema_choices, f"{label_prefix} Schema")
184
- dbutils.widgets.text(f"{prefix}_schema_override", "", f"{label_prefix} Schema (manual override)")
185
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)")
186
236
  dbutils.widgets.text(f"{prefix}_table_override", "", f"{label_prefix} Table (manual override)")
187
237
 
188
238
 
@@ -220,10 +270,19 @@ def _build_check_widgets() -> None:
220
270
  # and in a predictable order - Databricks lays widgets out in creation
221
271
  # order, so splitting these across cells made them interleave oddly and
222
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
223
280
  catalogs = _list_catalogs()
224
281
  _build_cascading_widgets("source", "Source")
225
282
  _build_cascading_widgets("target", "Target")
226
283
  _build_check_widgets()
284
+ _build_override_widgets("source", "Source")
285
+ _build_override_widgets("target", "Target")
227
286
 
228
287
  print(
229
288
  "Widgets refreshed for the CURRENT Catalog/Schema selection.\n"
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablevalidator-databricks
3
- Version: 0.1.6
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: typer>=0.12
9
- Requires-Dist: table-validator>=0.1.19
9
+ Requires-Dist: table-validator>=0.1.22
10
10
  Provides-Extra: dev
11
11
  Requires-Dist: pytest>=7.0; extra == "dev"
12
12
  Requires-Dist: ruff>=0.4; extra == "dev"
@@ -1,5 +1,5 @@
1
1
  typer>=0.12
2
- table-validator>=0.1.19
2
+ table-validator>=0.1.22
3
3
 
4
4
  [dev]
5
5
  pytest>=7.0
@@ -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,9 +103,12 @@ 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 "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
- match = re.search(r'pip install "table-validator>=([0-9]+)\.([0-9]+)\.([0-9]+)"', content)
109
+ match = re.search(
110
+ r'"table-validator>=([0-9]+)\.([0-9]+)\.([0-9]+)"', content
111
+ )
109
112
  assert match, "no parseable version floor in the %pip install line"
110
113
  major, minor, patch = (int(g) for g in match.groups())
111
114
  assert (major, minor, patch) >= (0, 1, 9)
@@ -131,3 +134,57 @@ def test_generated_notebook_stamps_the_generator_version(tmp_path: Path) -> None
131
134
  # And it explains that regenerating is what picks up new widgets.
132
135
  assert "upgrading the" in content.lower()
133
136
  assert "tablevalidator-databricks init" in content
137
+
138
+
139
+ def test_pip_install_line_forces_an_upgrade(tmp_path: Path) -> None:
140
+ """Real user-reported failure: `%pip install "table-validator"` printed
141
+ "Requirement already satisfied" and kept an older engine (0.1.20),
142
+ which then crashed on display(). The install must force an upgrade so
143
+ an existing older install is actually replaced."""
144
+ output = tmp_path / "TableValidator.py"
145
+ generate_notebook(output, "full")
146
+ content = output.read_text(encoding="utf-8")
147
+
148
+ assert "pip install --upgrade" in content
149
+ assert '"table-validator>=' in content
150
+
151
+
152
+ def test_notebook_reports_the_loaded_engine_version(tmp_path: Path) -> None:
153
+ """The version that matters is the one actually imported, not the one
154
+ the install cell asked for - the notebook must print it and warn when
155
+ it is below the required floor."""
156
+ output = tmp_path / "TableValidator.py"
157
+ generate_notebook(output, "full")
158
+ content = output.read_text(encoding="utf-8")
159
+
160
+ assert "table_validator.__version__" in content
161
+ assert "WARNING" in content
162
+ assert "ArrowTypeError" in content
163
+
164
+
165
+ def test_core_version_floor_includes_the_arrow_fix(tmp_path: Path) -> None:
166
+ """The floor must be at least 0.1.22, the first release whose result
167
+ sheets survive Arrow conversion (what Databricks' display() uses)."""
168
+ output = tmp_path / "TableValidator.py"
169
+ generate_notebook(output, "full")
170
+ content = output.read_text(encoding="utf-8")
171
+
172
+ match = re.search(r'table-validator>=([0-9]+)\.([0-9]+)\.([0-9]+)"', content)
173
+ assert match
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.6",
3
- "distance": 0,
4
- "node": "g85101fc",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-10"
8
- }