table-validator 0.1.20__tar.gz → 0.1.22__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.
- {table_validator-0.1.20/table_validator.egg-info → table_validator-0.1.22}/PKG-INFO +1 -1
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/main.py +10 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/notebook.py +35 -1
- {table_validator-0.1.20 → table_validator-0.1.22/table_validator.egg-info}/PKG-INFO +1 -1
- table_validator-0.1.22/table_validator.egg-info/scm_version.json +8 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_notebook.py +82 -0
- table_validator-0.1.20/table_validator.egg-info/scm_version.json +0 -8
- {table_validator-0.1.20 → table_validator-0.1.22}/LICENSE +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/README.md +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/pyproject.toml +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/setup.cfg +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/auth/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/auth/azure_auth.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/auth/databricks_auth.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/partition_prompt.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/summary_table.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/wizard.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/config/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/config/manager.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/config/schema.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/azure_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/base_sql_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/databricks_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/spark_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/engine/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/engine/comparison_engine.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/models.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/reports/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/reports/excel_report.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/blob_discovery.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/catalog_validator.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/mismatch_classifier.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/row_validator.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/SOURCES.txt +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/dependency_links.txt +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/entry_points.txt +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/requires.txt +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/scm_file_list.json +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/top_level.txt +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/__init__.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_base_sql_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_blob_discovery.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_catalog_validator.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_cli.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_databricks_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_excel_report.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_mismatch_classifier.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_package_import_without_pyspark.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_partition_prompt.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_report_command.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_row_validator.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_spark_connector.py +0 -0
- {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_wizard.py +0 -0
|
@@ -170,6 +170,16 @@ def info() -> None:
|
|
|
170
170
|
" tablevalidator report Print the summary table from an "
|
|
171
171
|
"existing report without opening it.\n"
|
|
172
172
|
"\n"
|
|
173
|
+
"Other ways to use this package:\n"
|
|
174
|
+
" Inside a Databricks notebook, call validate_tables() directly - "
|
|
175
|
+
"no credentials to configure, it reuses the notebook's own Spark "
|
|
176
|
+
"session. Run validate_tables.help() in a cell for the full "
|
|
177
|
+
"syntax.\n"
|
|
178
|
+
" Prefer filling in form fields over writing code? Install the "
|
|
179
|
+
"'tablevalidator-databricks' package and run "
|
|
180
|
+
"'tablevalidator-databricks info' - it generates a Databricks "
|
|
181
|
+
"notebook driven entirely by widgets.\n"
|
|
182
|
+
"\n"
|
|
173
183
|
"Run 'tablevalidator <command> --help' for a command's full "
|
|
174
184
|
"options (e.g. tablevalidator validate --help).\n"
|
|
175
185
|
"\n"
|
|
@@ -171,7 +171,41 @@ class ResultTable:
|
|
|
171
171
|
self.rows = rows
|
|
172
172
|
|
|
173
173
|
def to_dataframe(self) -> pd.DataFrame:
|
|
174
|
-
|
|
174
|
+
"""A real pandas DataFrame of this sheet.
|
|
175
|
+
|
|
176
|
+
Mixed-type columns are normalized first. A row-builder can put an
|
|
177
|
+
int in a cell for one table and "" or None in the same column for
|
|
178
|
+
another (e.g. "Row Count (Src)" is a real count for a table that
|
|
179
|
+
reached the row-count stage, and None for one that was SKIPPED
|
|
180
|
+
before it) - pandas then types that column as `object`, and
|
|
181
|
+
Databricks' display() fails converting it to Arrow with
|
|
182
|
+
"ArrowTypeError: Expected bytes, got a 'int' object", because
|
|
183
|
+
Arrow infers a string type from the object dtype and then meets
|
|
184
|
+
an int. Coercing each object column to a single consistent type
|
|
185
|
+
removes that ambiguity, so display(...) works on every sheet.
|
|
186
|
+
"""
|
|
187
|
+
df = pd.DataFrame(self.rows, columns=self.headers)
|
|
188
|
+
|
|
189
|
+
for col in df.columns:
|
|
190
|
+
if df[col].dtype != object:
|
|
191
|
+
continue
|
|
192
|
+
# Blank-ish placeholders and real nulls are the same thing to
|
|
193
|
+
# a reader - unify them so a column of numbers-plus-blanks is
|
|
194
|
+
# seen as numeric rather than mixed.
|
|
195
|
+
cleaned = df[col].map(lambda v: None if v is None or v == "" else v)
|
|
196
|
+
if cleaned.isna().all():
|
|
197
|
+
df[col] = cleaned.astype("string")
|
|
198
|
+
continue
|
|
199
|
+
numeric = pd.to_numeric(cleaned, errors="coerce")
|
|
200
|
+
if numeric.notna().sum() == cleaned.notna().sum():
|
|
201
|
+
# Every non-blank value really is a number.
|
|
202
|
+
df[col] = numeric
|
|
203
|
+
else:
|
|
204
|
+
# Anything else renders as text (statuses, percentages,
|
|
205
|
+
# mixed content) - str() each value so Arrow sees one type.
|
|
206
|
+
df[col] = cleaned.map(lambda v: None if v is None else str(v)).astype("string")
|
|
207
|
+
|
|
208
|
+
return df
|
|
175
209
|
|
|
176
210
|
def __len__(self) -> int:
|
|
177
211
|
return len(self.rows)
|
|
@@ -822,3 +822,85 @@ def test_enabled_validations_passed_through_when_given():
|
|
|
822
822
|
)
|
|
823
823
|
|
|
824
824
|
assert captured["request"].enabled_validations == {ValidationType.SCHEMA}
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
# ---------------------------------------------------------------------------
|
|
828
|
+
# to_dataframe() type normalization - real user-reported failure. Databricks'
|
|
829
|
+
# display() converts via Arrow, and a column holding an int for one table and
|
|
830
|
+
# "" / None for another gets object dtype, which Arrow rejects with
|
|
831
|
+
# "ArrowTypeError: Expected bytes, got a 'int' object". Every sheet must be
|
|
832
|
+
# Arrow-convertible.
|
|
833
|
+
# ---------------------------------------------------------------------------
|
|
834
|
+
def test_to_dataframe_normalizes_mixed_int_and_blank_column():
|
|
835
|
+
"""A column mixing real ints with blank placeholders must come back as
|
|
836
|
+
a single numeric dtype, not object."""
|
|
837
|
+
from table_validator.notebook import ResultTable
|
|
838
|
+
|
|
839
|
+
df = ResultTable(["Table", "Row Count"], [["a", 100], ["b", ""]]).to_dataframe()
|
|
840
|
+
|
|
841
|
+
assert df["Row Count"].dtype != object
|
|
842
|
+
assert df["Row Count"].iloc[0] == 100
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def test_to_dataframe_normalizes_mixed_int_and_none_column():
|
|
846
|
+
from table_validator.notebook import ResultTable
|
|
847
|
+
|
|
848
|
+
df = ResultTable(["Table", "Row Count"], [["a", 5], ["b", None]]).to_dataframe()
|
|
849
|
+
|
|
850
|
+
assert df["Row Count"].dtype != object
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def test_to_dataframe_keeps_genuine_text_columns_as_text():
|
|
854
|
+
"""Status columns and the like must stay readable, not be coerced to
|
|
855
|
+
NaN by the numeric path."""
|
|
856
|
+
from table_validator.notebook import ResultTable
|
|
857
|
+
|
|
858
|
+
df = ResultTable(["Status"], [["PASS"], ["FAIL"]]).to_dataframe()
|
|
859
|
+
|
|
860
|
+
assert list(df["Status"]) == ["PASS", "FAIL"]
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def test_to_dataframe_handles_a_column_of_only_blanks():
|
|
864
|
+
from table_validator.notebook import ResultTable
|
|
865
|
+
|
|
866
|
+
df = ResultTable(["Note"], [[""], [None]]).to_dataframe()
|
|
867
|
+
|
|
868
|
+
assert len(df) == 2
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def test_every_sheet_is_arrow_convertible():
|
|
872
|
+
"""End-to-end guard for the reported display() crash: convert every
|
|
873
|
+
sheet exactly the way Databricks' display() does."""
|
|
874
|
+
pa = pytest.importorskip("pyarrow")
|
|
875
|
+
|
|
876
|
+
table_ok = TableValidationResult(
|
|
877
|
+
schema_name="sch", table="t1", status=ValidationStatus.PASS,
|
|
878
|
+
exists_in_source=True, exists_in_target=True,
|
|
879
|
+
row_count_source=100, row_count_target=100,
|
|
880
|
+
)
|
|
881
|
+
# A SKIPPED table leaves the row counts as None - this is what mixes
|
|
882
|
+
# types within a single column.
|
|
883
|
+
table_skipped = TableValidationResult(
|
|
884
|
+
schema_name="sch", table="t2", status=ValidationStatus.SKIPPED,
|
|
885
|
+
exists_in_source=True, exists_in_target=True,
|
|
886
|
+
)
|
|
887
|
+
response = CatalogValidationResponse(
|
|
888
|
+
source_catalog="c1", target_catalog="c2", status=ValidationStatus.PASS,
|
|
889
|
+
schemas=[SchemaValidationResult(
|
|
890
|
+
schema_name="sch", status=ValidationStatus.PASS,
|
|
891
|
+
tables=[table_ok, table_skipped],
|
|
892
|
+
)],
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
with patch("table_validator.notebook.SparkConnector", return_value=MagicMock()), \
|
|
896
|
+
patch(
|
|
897
|
+
"table_validator.notebook.CatalogValidator.compare_catalogs",
|
|
898
|
+
lambda self, request: response,
|
|
899
|
+
):
|
|
900
|
+
result = validate_tables("c1.sch", "c2.sch", spark=MagicMock())
|
|
901
|
+
|
|
902
|
+
for sheet in (
|
|
903
|
+
"table_validation", "column_validation", "data_mismatches",
|
|
904
|
+
"row_hash_mismatches", "mismatch_categories", "suggestions",
|
|
905
|
+
):
|
|
906
|
+
pa.Table.from_pandas(getattr(result, sheet).to_dataframe())
|
|
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
|
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/azure_connector.py
RENAMED
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/base_sql_connector.py
RENAMED
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/databricks_connector.py
RENAMED
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/spark_connector.py
RENAMED
|
File without changes
|
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/engine/comparison_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/blob_discovery.py
RENAMED
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/catalog_validator.py
RENAMED
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/mismatch_classifier.py
RENAMED
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/row_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/scm_file_list.json
RENAMED
|
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
|
{table_validator-0.1.20 → table_validator-0.1.22}/tests/test_package_import_without_pyspark.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|