table-validator 0.1.16__tar.gz → 0.1.18__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 (56) hide show
  1. {table_validator-0.1.16/table_validator.egg-info → table_validator-0.1.18}/PKG-INFO +1 -1
  2. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/cli/main.py +11 -0
  3. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/cli/wizard.py +58 -3
  4. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/config/schema.py +32 -0
  5. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/connectors/base_sql_connector.py +20 -2
  6. {table_validator-0.1.16 → table_validator-0.1.18/table_validator.egg-info}/PKG-INFO +1 -1
  7. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/scm_version.json +2 -2
  8. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_base_sql_connector.py +73 -0
  9. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_cli.py +59 -0
  10. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_wizard.py +13 -0
  11. {table_validator-0.1.16 → table_validator-0.1.18}/LICENSE +0 -0
  12. {table_validator-0.1.16 → table_validator-0.1.18}/README.md +0 -0
  13. {table_validator-0.1.16 → table_validator-0.1.18}/pyproject.toml +0 -0
  14. {table_validator-0.1.16 → table_validator-0.1.18}/setup.cfg +0 -0
  15. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/__init__.py +0 -0
  16. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/auth/__init__.py +0 -0
  17. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/auth/azure_auth.py +0 -0
  18. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/auth/databricks_auth.py +0 -0
  19. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/cli/__init__.py +0 -0
  20. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/cli/partition_prompt.py +0 -0
  21. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/cli/summary_table.py +0 -0
  22. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/config/__init__.py +0 -0
  23. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/config/manager.py +0 -0
  24. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/connectors/__init__.py +0 -0
  25. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/connectors/azure_connector.py +0 -0
  26. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/connectors/databricks_connector.py +0 -0
  27. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/connectors/spark_connector.py +0 -0
  28. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/engine/__init__.py +0 -0
  29. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/engine/comparison_engine.py +0 -0
  30. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/models.py +0 -0
  31. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/notebook.py +0 -0
  32. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/reports/__init__.py +0 -0
  33. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/reports/excel_report.py +0 -0
  34. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/validators/__init__.py +0 -0
  35. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/validators/blob_discovery.py +0 -0
  36. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/validators/catalog_validator.py +0 -0
  37. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/validators/mismatch_classifier.py +0 -0
  38. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator/validators/row_validator.py +0 -0
  39. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/SOURCES.txt +0 -0
  40. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/dependency_links.txt +0 -0
  41. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/entry_points.txt +0 -0
  42. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/requires.txt +0 -0
  43. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/scm_file_list.json +0 -0
  44. {table_validator-0.1.16 → table_validator-0.1.18}/table_validator.egg-info/top_level.txt +0 -0
  45. {table_validator-0.1.16 → table_validator-0.1.18}/tests/__init__.py +0 -0
  46. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_blob_discovery.py +0 -0
  47. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_catalog_validator.py +0 -0
  48. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_databricks_connector.py +0 -0
  49. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_excel_report.py +0 -0
  50. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_mismatch_classifier.py +0 -0
  51. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_notebook.py +0 -0
  52. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_package_import_without_pyspark.py +0 -0
  53. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_partition_prompt.py +0 -0
  54. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_report_command.py +0 -0
  55. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_row_validator.py +0 -0
  56. {table_validator-0.1.16 → table_validator-0.1.18}/tests/test_spark_connector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-validator
3
- Version: 0.1.16
3
+ Version: 0.1.18
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
@@ -507,6 +507,17 @@ def _run_databricks_validation(
507
507
  column_map=config.column_map,
508
508
  )
509
509
 
510
+ # Row-filter predicate - lives purely as connector instance state
511
+ # (BaseSqlConnector.set_row_filters/_scoped_table), same mechanism as
512
+ # the notebook-native validate_tables() API, not a CatalogValidationRequest
513
+ # field. A no-op unless the user configured at least one of the three.
514
+ if config.row_filter or config.source_row_filter or config.target_row_filter:
515
+ databricks.set_row_filters(
516
+ common=config.row_filter,
517
+ source=config.source_row_filter,
518
+ target=config.target_row_filter,
519
+ )
520
+
510
521
  partition_prompt = build_partition_prompt(yes=yes)
511
522
  validator = CatalogValidator(databricks, partition_prompt=partition_prompt)
512
523
  return validator.compare_catalogs(request)
@@ -192,6 +192,57 @@ def _prompt_column_customization(config: ValidatorConfig) -> None:
192
192
  ) or []
193
193
 
194
194
 
195
+ def _prompt_row_filter(config: ValidatorConfig) -> None:
196
+ """Optional row-filter predicate, asked right after column
197
+ customization - same single-table scope, Databricks source only
198
+ (AzureSqlConnector has no row-filter mechanism, unlike
199
+ BaseSqlConnector's set_row_filters/_scoped_table that the Databricks
200
+ connector and the notebook-native validate_tables() API both share).
201
+ Skipped entirely (leaving any existing row_filter/source_row_filter/
202
+ target_row_filter untouched) unless the user opts in, so declining
203
+ gives identical behavior to before this feature existed."""
204
+ customize = questionary.confirm(
205
+ "Filter to a subset of rows? (only rows matching a condition are "
206
+ "compared, e.g. \"id > 10 and id < 30\" or \"gender = 'male'\")",
207
+ default=False,
208
+ ).ask()
209
+
210
+ if not customize:
211
+ return
212
+
213
+ same_condition = questionary.confirm(
214
+ "Apply the same condition to both source and target?",
215
+ default=True,
216
+ ).ask()
217
+
218
+ # Mutually exclusive in this UI - picking one must clear whatever
219
+ # stale value the OTHER carried over from an earlier configure run,
220
+ # same convention as _prompt_schema_scoping below.
221
+ if same_condition:
222
+ config.row_filter = _ask(
223
+ questionary.text(
224
+ "Row filter condition (SQL WHERE-fragment):",
225
+ default=config.row_filter or "",
226
+ )
227
+ ) or None
228
+ config.source_row_filter = None
229
+ config.target_row_filter = None
230
+ else:
231
+ config.row_filter = None
232
+ config.source_row_filter = _ask(
233
+ questionary.text(
234
+ "Source row filter condition:",
235
+ default=config.source_row_filter or "",
236
+ )
237
+ ) or None
238
+ config.target_row_filter = _ask(
239
+ questionary.text(
240
+ "Target row filter condition:",
241
+ default=config.target_row_filter or "",
242
+ )
243
+ ) or None
244
+
245
+
195
246
  def _prompt_schema_scoping(config: ValidatorConfig) -> None:
196
247
  """
197
248
  Optional schema-wide scoping, asked only when a schema is named on
@@ -725,10 +776,11 @@ def run_configure_wizard() -> None:
725
776
  # identical behavior to before this feature existed.
726
777
  typer.echo("\n== Customize validation (optional) ==")
727
778
  _prompt_column_customization(config)
728
- # Column-name mapping (renamed columns) - Databricks-to-Databricks
729
- # only, since column_map/CatalogValidator don't apply to the
730
- # Azure Blob/SQL source paths.
779
+ # Row-filter predicate and column-name mapping (renamed columns) -
780
+ # Databricks-to-Databricks only, since set_row_filters/column_map/
781
+ # CatalogValidator don't apply to the Azure Blob/SQL source paths.
731
782
  if config.source_type == SourceType.DATABRICKS:
783
+ _prompt_row_filter(config)
732
784
  _prompt_column_mapping(config, secrets)
733
785
  else:
734
786
  config.primary_key = None
@@ -736,6 +788,9 @@ def run_configure_wizard() -> None:
736
788
  config.ignore_columns = []
737
789
  config.ignore_datatype_columns = []
738
790
  config.column_map = {}
791
+ config.row_filter = None
792
+ config.source_row_filter = None
793
+ config.target_row_filter = None
739
794
 
740
795
  # Schema-wide sweep scoping - only meaningful when a schema is named
741
796
  # on both sides but no specific table is (the single-table branch
@@ -314,6 +314,38 @@ class ValidatorConfig(BaseModel):
314
314
  ),
315
315
  )
316
316
 
317
+ row_filter: Optional[str] = Field(
318
+ default=None,
319
+ description=(
320
+ "Optional SQL WHERE-fragment restricting comparison to matching "
321
+ "rows on BOTH sides (row count, statistics, row-hash/column "
322
+ "diff) - e.g. 'id > 20 and id < 100' or \"gender = 'male'\". "
323
+ "Only meaningful for the single named table in source_table/"
324
+ "target_table (Databricks source only). Combined with "
325
+ "source_row_filter/target_row_filter via AND if both are set. "
326
+ "Used as-is, not parsed or validated - a typo surfaces as a "
327
+ "normal SQL error when the query runs."
328
+ ),
329
+ )
330
+
331
+ source_row_filter: Optional[str] = Field(
332
+ default=None,
333
+ description=(
334
+ "Optional SQL WHERE-fragment applied only to the source side, "
335
+ "ANDed with row_filter if both are set. Use this (with "
336
+ "target_row_filter) instead of row_filter when the condition "
337
+ "must differ per side."
338
+ ),
339
+ )
340
+
341
+ target_row_filter: Optional[str] = Field(
342
+ default=None,
343
+ description=(
344
+ "Optional SQL WHERE-fragment applied only to the target side, "
345
+ "ANDed with row_filter if both are set."
346
+ ),
347
+ )
348
+
317
349
  blob_source: BlobSourceConfig = Field(default_factory=BlobSourceConfig)
318
350
  sql_source: SqlSourceConfig = Field(default_factory=SqlSourceConfig)
319
351
  synapse_source: SynapseSourceConfig = Field(default_factory=SynapseSourceConfig)
@@ -528,6 +528,24 @@ class BaseSqlConnector:
528
528
  return None
529
529
  return int(value)
530
530
 
531
+ def _nan_to_none(value: Any) -> Any:
532
+ """
533
+ MIN/MAX over ZERO matching rows (e.g. both sides legitimately
534
+ empty under the same row_filter) is SQL NULL, which arrives
535
+ here as float NaN after the spark.sql(...).toPandas() round-
536
+ trip - same underlying issue as _to_int_or_none above, but
537
+ min/max must stay their original type (a string, date, etc.)
538
+ when a real value IS present, so this only strips NaN rather
539
+ than casting to int. Without this, two equally-empty sides
540
+ each report NaN for min/max, and compare_min_max's `==`
541
+ check treats NaN != NaN (the standard IEEE 754 quirk) as a
542
+ real difference - a false FAIL on a table with nothing to
543
+ compare, not an actual mismatch.
544
+ """
545
+ if isinstance(value, float) and value != value:
546
+ return None
547
+ return value
548
+
531
549
  for col in columns:
532
550
  entry: Dict[str, Any] = {
533
551
  "null_count": _to_int_or_none(row.get(f"{col}__nulls")),
@@ -536,8 +554,8 @@ class BaseSqlConnector:
536
554
  "max": None,
537
555
  }
538
556
  if col.lower() in min_max_set:
539
- entry["min"] = row.get(f"{col}__min")
540
- entry["max"] = row.get(f"{col}__max")
557
+ entry["min"] = _nan_to_none(row.get(f"{col}__min"))
558
+ entry["max"] = _nan_to_none(row.get(f"{col}__max"))
541
559
  result[col] = entry
542
560
 
543
561
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-validator
3
- Version: 0.1.16
3
+ Version: 0.1.18
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
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.1.16",
2
+ "tag": "0.1.18",
3
3
  "distance": 0,
4
- "node": "g676da730fd4229d0b784a52582b61554b052b4bb",
4
+ "node": "g442d915637cacc0f99f5a1eb372c1cc7128f7e06",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-09-03"
@@ -198,3 +198,76 @@ def test_get_column_statistics_still_converts_real_values_to_int():
198
198
  assert stats["id"]["distinct_count"] == 42
199
199
  assert isinstance(stats["id"]["null_count"], int)
200
200
  assert isinstance(stats["id"]["distinct_count"], int)
201
+
202
+
203
+ def test_get_column_statistics_min_max_nan_becomes_none():
204
+ """Real bug found via live user testing: comparing a table against
205
+ ITSELF with the exact same row_filter on both sides (matching zero
206
+ rows) still reported Overall Status FAIL, specifically Min/Max FAIL -
207
+ MIN/MAX over zero rows is SQL NULL -> NaN after the pandas round-
208
+ trip, and NaN != NaN (IEEE 754), so compare_min_max's `==` check
209
+ treated two equally-empty, equally-NaN sides as a real difference.
210
+ min/max must come back as None (not NaN) so two empty sides compare
211
+ equal."""
212
+ fake_spark = MagicMock()
213
+ fake_spark.sql.return_value.toPandas.return_value = pd.DataFrame(
214
+ {"id__nulls": [float("nan")], "id__distinct": [float("nan")],
215
+ "id__min": [float("nan")], "id__max": [float("nan")]}
216
+ )
217
+ connector = SparkConnector(spark=fake_spark)
218
+
219
+ stats = connector.get_column_statistics(
220
+ "cat", "sch", "tbl", ["id"], min_max_columns=["id"],
221
+ )
222
+
223
+ assert stats["id"]["min"] is None
224
+ assert stats["id"]["max"] is None
225
+
226
+
227
+ def test_get_column_statistics_min_max_real_values_pass_through_unchanged():
228
+ """Regression guard alongside the NaN fix above - a genuine min/max
229
+ result (of any type - string, date, number) must pass through
230
+ completely unchanged, not be mistaken for NaN."""
231
+ fake_spark = MagicMock()
232
+ fake_spark.sql.return_value.toPandas.return_value = pd.DataFrame(
233
+ {"name__nulls": [0], "name__distinct": [5],
234
+ "name__min": ["Alice"], "name__max": ["Zoe"]}
235
+ )
236
+ connector = SparkConnector(spark=fake_spark)
237
+
238
+ stats = connector.get_column_statistics(
239
+ "cat", "sch", "tbl", ["name"], min_max_columns=["name"],
240
+ )
241
+
242
+ assert stats["name"]["min"] == "Alice"
243
+ assert stats["name"]["max"] == "Zoe"
244
+
245
+
246
+ def test_two_identically_filtered_empty_sides_compare_as_pass():
247
+ """End-to-end regression test for the exact reported scenario: the
248
+ SAME table compared against itself with the SAME row_filter matching
249
+ zero rows on both sides must report min/max as equal (both None),
250
+ not a false mismatch."""
251
+ from table_validator.validators.catalog_validator import CatalogValidator
252
+
253
+ fake_spark = MagicMock()
254
+ connector = SparkConnector(spark=fake_spark)
255
+ connector.set_row_filters(common="gender = 'male'")
256
+
257
+ fake_spark.sql.return_value.toPandas.return_value = pd.DataFrame(
258
+ {"id__nulls": [float("nan")], "id__distinct": [float("nan")],
259
+ "id__min": [float("nan")], "id__max": [float("nan")]}
260
+ )
261
+ source_stats = connector.get_column_statistics(
262
+ "cat", "sch", "src_tbl", ["id"], min_max_columns=["id"], side="source",
263
+ )
264
+ target_stats = connector.get_column_statistics(
265
+ "cat", "sch", "tgt_tbl", ["id"], min_max_columns=["id"], side="target",
266
+ )
267
+
268
+ validator = CatalogValidator(connector)
269
+ status = validator.compare_min_max(
270
+ source_stats["id"]["min"], source_stats["id"]["max"],
271
+ target_stats["id"]["min"], target_stats["id"]["max"],
272
+ )
273
+ assert status.value == "PASS"
@@ -1437,6 +1437,65 @@ def test_validate_databricks_wires_only_columns_ignore_columns_and_ignore_dataty
1437
1437
  assert request.column_map == {"cust_id": "customer_id"}
1438
1438
 
1439
1439
 
1440
+ def test_validate_databricks_wires_row_filters_to_connector(tmp_path: Path) -> None:
1441
+ """config.row_filter/source_row_filter/target_row_filter must reach the
1442
+ Databricks connector via set_row_filters() before compare_catalogs runs -
1443
+ these live purely as connector instance state (same mechanism as the
1444
+ notebook-native validate_tables() API), never as CatalogValidationRequest
1445
+ fields."""
1446
+ config_path = _full_config(tmp_path)
1447
+ config = load_config(config_path)
1448
+ config.row_filter = "status = 'active'"
1449
+ config.source_row_filter = "id > 20"
1450
+ config.target_row_filter = "id > 15"
1451
+ save_config(config, config_path)
1452
+ output_path = tmp_path / "validation_report.xlsx"
1453
+
1454
+ mock_connector = _mock_databricks_connector()
1455
+
1456
+ with patch(
1457
+ "table_validator.cli.main.DatabricksConnector", return_value=mock_connector
1458
+ ), patch(
1459
+ "table_validator.cli.main.get_databricks_token", return_value="dapi_fake"
1460
+ ), patch(
1461
+ "table_validator.cli.main.get_azure_credential", return_value=None
1462
+ ):
1463
+ runner.invoke(
1464
+ app,
1465
+ ["validate", "--config-path", str(config_path), "--output", str(output_path)],
1466
+ )
1467
+
1468
+ mock_connector.set_row_filters.assert_called_once_with(
1469
+ common="status = 'active'", source="id > 20", target="id > 15",
1470
+ )
1471
+
1472
+
1473
+ def test_validate_databricks_skips_set_row_filters_when_none_configured(
1474
+ tmp_path: Path,
1475
+ ) -> None:
1476
+ """A config with no row filters set must never call set_row_filters at
1477
+ all - regression guard so a table without this feature configured
1478
+ behaves identically to before the feature existed."""
1479
+ config_path = _full_config(tmp_path)
1480
+ output_path = tmp_path / "validation_report.xlsx"
1481
+
1482
+ mock_connector = _mock_databricks_connector()
1483
+
1484
+ with patch(
1485
+ "table_validator.cli.main.DatabricksConnector", return_value=mock_connector
1486
+ ), patch(
1487
+ "table_validator.cli.main.get_databricks_token", return_value="dapi_fake"
1488
+ ), patch(
1489
+ "table_validator.cli.main.get_azure_credential", return_value=None
1490
+ ):
1491
+ runner.invoke(
1492
+ app,
1493
+ ["validate", "--config-path", str(config_path), "--output", str(output_path)],
1494
+ )
1495
+
1496
+ mock_connector.set_row_filters.assert_not_called()
1497
+
1498
+
1440
1499
  def test_validate_mode_stats_stops_before_fingerprint_and_row_hash(tmp_path: Path) -> None:
1441
1500
  """--mode stats is the CLI-flag equivalent of the tiered funnel's
1442
1501
  "statistical only" prompt: it must stop after Tier 1 even though the
@@ -343,6 +343,7 @@ def test_deselecting_row_only_omits_row(tmp_path, monkeypatch):
343
343
  "tgt_cat", "silver", "customers",
344
344
  "", # primary key (blank)
345
345
  False, # customize validation? (declined)
346
+ False, # filter to a subset of rows? (declined)
346
347
  ["catalog & schema", "column"], # validations checkbox: row unchecked
347
348
  ])
348
349
 
@@ -417,6 +418,7 @@ def test_databricks_source_type_prompts_for_source_table(tmp_path, monkeypatch):
417
418
  "tgt_cat", "silver", "customers", # target table
418
419
  "", # primary key (left blank)
419
420
  False, # customize validation? (declined)
421
+ False, # filter to a subset of rows? (declined)
420
422
  [], # validations
421
423
  ])
422
424
 
@@ -442,6 +444,7 @@ def test_databricks_source_type_prompts_for_primary_key_when_both_tables_named(
442
444
  "tgt_cat", "silver", "customers",
443
445
  "id, region_id ", # primary key, comma-separated with stray spaces
444
446
  False, # customize validation? (declined)
447
+ False, # filter to a subset of rows? (declined)
445
448
  [],
446
449
  ])
447
450
 
@@ -462,6 +465,7 @@ def test_databricks_wizard_declining_customization_leaves_defaults_untouched(
462
465
  "tgt_cat", "silver", "customers",
463
466
  "", # primary key (left blank)
464
467
  False, # customize validation? (declined)
468
+ False, # filter to a subset of rows? (declined)
465
469
  [],
466
470
  ])
467
471
 
@@ -486,6 +490,7 @@ def test_databricks_wizard_customize_populates_all_three_column_lists(
486
490
  "id, name ", # only_columns, stray spaces
487
491
  "updated_at", # ignore_columns
488
492
  "legacy_flag, notes", # ignore_datatype_columns
493
+ False, # filter to a subset of rows? (declined)
489
494
  [],
490
495
  ])
491
496
 
@@ -508,6 +513,7 @@ def test_databricks_wizard_customize_all_three_left_blank(tmp_path, monkeypatch)
508
513
  "", # only_columns left blank
509
514
  "", # ignore_columns left blank
510
515
  "", # ignore_datatype_columns left blank
516
+ False, # filter to a subset of rows? (declined)
511
517
  [],
512
518
  ])
513
519
 
@@ -811,6 +817,7 @@ def test_column_mapping_skipped_when_no_token_available(tmp_path, monkeypatch):
811
817
  "tgt_cat", "silver", "customers",
812
818
  "id",
813
819
  False, # customize validation? (declined)
820
+ False, # filter to a subset of rows? (declined)
814
821
  [],
815
822
  ])
816
823
 
@@ -834,6 +841,7 @@ def test_column_mapping_live_connection_success_populates_map(tmp_path, monkeypa
834
841
  "tgt_cat", "silver", "customers",
835
842
  "id",
836
843
  False, # customize validation? (declined)
844
+ False, # filter to a subset of rows? (declined)
837
845
  "customer_id", # column mapping: cust_id -> customer_id
838
846
  [],
839
847
  ],
@@ -859,6 +867,7 @@ def test_column_mapping_live_connection_failure_skips_gracefully(tmp_path, monke
859
867
  "tgt_cat", "silver", "customers",
860
868
  "id",
861
869
  False, # customize validation? (declined)
870
+ False, # filter to a subset of rows? (declined)
862
871
  [],
863
872
  ],
864
873
  databricks_token="dapi_fake",
@@ -889,6 +898,7 @@ def test_column_mapping_only_asks_about_unmatched_source_columns(tmp_path, monke
889
898
  "tgt_cat", "silver", "customers",
890
899
  "id",
891
900
  False,
901
+ False, # filter to a subset of rows? (declined)
892
902
  "customer_id", # only ONE column-mapping answer, for cust_id
893
903
  [],
894
904
  ],
@@ -916,6 +926,7 @@ def test_column_mapping_skip_leaves_column_unmapped(tmp_path, monkeypatch):
916
926
  "tgt_cat", "silver", "customers",
917
927
  "id",
918
928
  False,
929
+ False, # filter to a subset of rows? (declined)
919
930
  "(skip this column)",
920
931
  [],
921
932
  ],
@@ -941,6 +952,7 @@ def test_column_mapping_no_unmatched_columns_asks_nothing(tmp_path, monkeypatch)
941
952
  "tgt_cat", "silver", "customers",
942
953
  "id",
943
954
  False,
955
+ False, # filter to a subset of rows? (declined)
944
956
  # no column-mapping answer - none should be consumed
945
957
  [],
946
958
  ],
@@ -993,6 +1005,7 @@ def test_schema_scoping_prompt_skipped_when_a_table_is_named(tmp_path, monkeypat
993
1005
  "tgt_cat", "silver", "customers",
994
1006
  "", # primary key (blank)
995
1007
  False, # customize validation? (declined)
1008
+ False, # filter to a subset of rows? (declined)
996
1009
  # no schema-scoping answer here - none should be consumed
997
1010
  [],
998
1011
  ])