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.
Files changed (57) hide show
  1. {table_validator-0.1.20/table_validator.egg-info → table_validator-0.1.22}/PKG-INFO +1 -1
  2. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/main.py +10 -0
  3. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/notebook.py +35 -1
  4. {table_validator-0.1.20 → table_validator-0.1.22/table_validator.egg-info}/PKG-INFO +1 -1
  5. table_validator-0.1.22/table_validator.egg-info/scm_version.json +8 -0
  6. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_notebook.py +82 -0
  7. table_validator-0.1.20/table_validator.egg-info/scm_version.json +0 -8
  8. {table_validator-0.1.20 → table_validator-0.1.22}/LICENSE +0 -0
  9. {table_validator-0.1.20 → table_validator-0.1.22}/README.md +0 -0
  10. {table_validator-0.1.20 → table_validator-0.1.22}/pyproject.toml +0 -0
  11. {table_validator-0.1.20 → table_validator-0.1.22}/setup.cfg +0 -0
  12. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/__init__.py +0 -0
  13. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/auth/__init__.py +0 -0
  14. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/auth/azure_auth.py +0 -0
  15. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/auth/databricks_auth.py +0 -0
  16. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/__init__.py +0 -0
  17. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/partition_prompt.py +0 -0
  18. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/summary_table.py +0 -0
  19. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/cli/wizard.py +0 -0
  20. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/config/__init__.py +0 -0
  21. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/config/manager.py +0 -0
  22. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/config/schema.py +0 -0
  23. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/__init__.py +0 -0
  24. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/azure_connector.py +0 -0
  25. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/base_sql_connector.py +0 -0
  26. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/databricks_connector.py +0 -0
  27. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/connectors/spark_connector.py +0 -0
  28. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/engine/__init__.py +0 -0
  29. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/engine/comparison_engine.py +0 -0
  30. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/models.py +0 -0
  31. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/reports/__init__.py +0 -0
  32. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/reports/excel_report.py +0 -0
  33. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/__init__.py +0 -0
  34. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/blob_discovery.py +0 -0
  35. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/catalog_validator.py +0 -0
  36. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/mismatch_classifier.py +0 -0
  37. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator/validators/row_validator.py +0 -0
  38. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/SOURCES.txt +0 -0
  39. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/dependency_links.txt +0 -0
  40. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/entry_points.txt +0 -0
  41. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/requires.txt +0 -0
  42. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/scm_file_list.json +0 -0
  43. {table_validator-0.1.20 → table_validator-0.1.22}/table_validator.egg-info/top_level.txt +0 -0
  44. {table_validator-0.1.20 → table_validator-0.1.22}/tests/__init__.py +0 -0
  45. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_base_sql_connector.py +0 -0
  46. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_blob_discovery.py +0 -0
  47. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_catalog_validator.py +0 -0
  48. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_cli.py +0 -0
  49. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_databricks_connector.py +0 -0
  50. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_excel_report.py +0 -0
  51. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_mismatch_classifier.py +0 -0
  52. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_package_import_without_pyspark.py +0 -0
  53. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_partition_prompt.py +0 -0
  54. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_report_command.py +0 -0
  55. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_row_validator.py +0 -0
  56. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_spark_connector.py +0 -0
  57. {table_validator-0.1.20 → table_validator-0.1.22}/tests/test_wizard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-validator
3
- Version: 0.1.20
3
+ Version: 0.1.22
4
4
  Summary: CLI tool for validating data migrations between Azure (Blob Storage / SQL Database) and Databricks Delta Lake catalogs
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -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
- return pd.DataFrame(self.rows, columns=self.headers)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-validator
3
- Version: 0.1.20
3
+ Version: 0.1.22
4
4
  Summary: CLI tool for validating data migrations between Azure (Blob Storage / SQL Database) and Databricks Delta Lake catalogs
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.1.22",
3
+ "distance": 0,
4
+ "node": "g9bb35af8f8b4c9edbf94bf61c68df2aed7fbfe08",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-10"
8
+ }
@@ -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())
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.1.20",
3
- "distance": 0,
4
- "node": "g9124e37c7cb704b705524188c1a7fec5d1848039",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-08"
8
- }