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.
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/PKG-INFO +2 -2
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/pyproject.toml +6 -1
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/templates/widget_notebook.py.tmpl +69 -10
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/PKG-INFO +2 -2
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/requires.txt +1 -1
- tablevalidator_databricks-0.1.8/tablevalidator_databricks.egg-info/scm_version.json +8 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tests/test_generated_notebook_execution.py +73 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tests/test_generator.py +59 -2
- tablevalidator_databricks-0.1.6/tablevalidator_databricks.egg-info/scm_version.json +0 -8
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/README.md +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/setup.cfg +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/__init__.py +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/__init__.py +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/cli/main.py +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/__init__.py +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks/generator/generator.py +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/SOURCES.txt +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/dependency_links.txt +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/entry_points.txt +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/scm_file_list.json +0 -0
- {tablevalidator_databricks-0.1.6 → tablevalidator_databricks-0.1.8}/tablevalidator_databricks.egg-info/top_level.txt +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 "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.
|
|
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.
|
|
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"
|
|
@@ -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
|
|
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(
|
|
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
|
|
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
|