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.
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/PKG-INFO +1 -1
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/templates/widget_notebook.py.tmpl +40 -10
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/PKG-INFO +1 -1
- tablevalidator_databricks-0.1.8/tablevalidator_databricks.egg-info/scm_version.json +8 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tests/test_generated_notebook_execution.py +73 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tests/test_generator.py +21 -3
- tablevalidator_databricks-0.1.7/tablevalidator_databricks.egg-info/scm_version.json +0 -8
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/README.md +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/pyproject.toml +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/setup.cfg +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/__init__.py +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/__init__.py +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/main.py +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/__init__.py +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/generator.py +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/SOURCES.txt +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/dependency_links.txt +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/entry_points.txt +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/requires.txt +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/scm_file_list.json +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/top_level.txt +0 -0
- {tablevalidator_databricks-0.1.7 → tablevalidator_databricks-0.1.8}/tests/test_cli.py +0 -0
|
@@ -42,18 +42,25 @@
|
|
|
42
42
|
# COMMAND ----------
|
|
43
43
|
|
|
44
44
|
# MAGIC %md ### Install the validation engine
|
|
45
|
-
# MAGIC
|
|
46
|
-
# MAGIC
|
|
47
|
-
# MAGIC
|
|
48
|
-
# MAGIC
|
|
49
|
-
# MAGIC
|
|
50
|
-
# MAGIC
|
|
51
|
-
# MAGIC
|
|
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"
|
|
@@ -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
|
|
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'
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|