|
1 | 1 | """Tests for DataFrame row validation with Pydantic.""" |
2 | 2 |
|
| 3 | +from typing import Any |
| 4 | + |
3 | 5 | import numpy as np |
4 | 6 | import pandas as pd |
5 | 7 | import polars as pl |
@@ -297,3 +299,87 @@ def test_whitespace_stripping() -> None: |
297 | 299 |
|
298 | 300 | # Should pass because ConfigDict strips whitespace |
299 | 301 | validate_dataframe_rows(df, SimpleValidator) |
| 302 | + |
| 303 | + |
| 304 | +def test_unknown_dataframe_type() -> None: |
| 305 | + require_pydantic() |
| 306 | + |
| 307 | + class NotADataFrame: |
| 308 | + pass |
| 309 | + |
| 310 | + fake_df = NotADataFrame() |
| 311 | + |
| 312 | + with pytest.raises(TypeError, match="Unknown DataFrame type"): |
| 313 | + from daffy.row_validation import _iterate_dataframe_with_index |
| 314 | + |
| 315 | + list(_iterate_dataframe_with_index(fake_df)) # type: ignore[arg-type] |
| 316 | + |
| 317 | + |
| 318 | +def test_iterative_validation_fallback(mocker: Any) -> None: |
| 319 | + require_pydantic() |
| 320 | + |
| 321 | + df = pd.DataFrame( |
| 322 | + { |
| 323 | + "name": ["Alice", "Bob", "Charlie"], |
| 324 | + "age": [25, -5, 150], # Two invalid ages |
| 325 | + "price": [10.5, 20.0, 30.0], |
| 326 | + } |
| 327 | + ) |
| 328 | + |
| 329 | + # Mock TypeAdapter to raise TypeError, forcing iterative validation |
| 330 | + from daffy import row_validation |
| 331 | + |
| 332 | + original_adapter = row_validation.TypeAdapter |
| 333 | + assert original_adapter is not None |
| 334 | + |
| 335 | + def mock_adapter(*args: Any, **kwargs: Any) -> Any: |
| 336 | + adapter = original_adapter(*args, **kwargs) # type: ignore[misc] |
| 337 | + |
| 338 | + def failing_validate(*args: Any, **kwargs: Any) -> Any: |
| 339 | + raise TypeError("Forced fallback to iterative") |
| 340 | + |
| 341 | + adapter.validate_python = failing_validate |
| 342 | + return adapter |
| 343 | + |
| 344 | + mocker.patch.object(row_validation, "TypeAdapter", side_effect=mock_adapter) |
| 345 | + |
| 346 | + with pytest.raises(AssertionError) as exc_info: |
| 347 | + validate_dataframe_rows(df, SimpleValidator, max_errors=5) |
| 348 | + |
| 349 | + message = str(exc_info.value) |
| 350 | + assert "Row validation failed" in message |
| 351 | + assert "Row 1:" in message or "Row 2:" in message |
| 352 | + |
| 353 | + |
| 354 | +def test_iterative_validation_fallback_polars(mocker: Any) -> None: |
| 355 | + require_pydantic() |
| 356 | + |
| 357 | + df = pl.DataFrame( |
| 358 | + { |
| 359 | + "name": ["Alice", "Bob", "Charlie"], |
| 360 | + "age": [25, -5, 150], |
| 361 | + "price": [10.5, 20.0, 30.0], |
| 362 | + } |
| 363 | + ) |
| 364 | + |
| 365 | + from daffy import row_validation |
| 366 | + |
| 367 | + original_adapter = row_validation.TypeAdapter |
| 368 | + assert original_adapter is not None |
| 369 | + |
| 370 | + def mock_adapter(*args: Any, **kwargs: Any) -> Any: |
| 371 | + adapter = original_adapter(*args, **kwargs) # type: ignore[misc] |
| 372 | + |
| 373 | + def failing_validate(*args: Any, **kwargs: Any) -> Any: |
| 374 | + raise TypeError("Forced fallback to iterative") |
| 375 | + |
| 376 | + adapter.validate_python = failing_validate |
| 377 | + return adapter |
| 378 | + |
| 379 | + mocker.patch.object(row_validation, "TypeAdapter", side_effect=mock_adapter) |
| 380 | + |
| 381 | + with pytest.raises(AssertionError) as exc_info: |
| 382 | + validate_dataframe_rows(df, SimpleValidator, max_errors=5) |
| 383 | + |
| 384 | + message = str(exc_info.value) |
| 385 | + assert "Row validation failed" in message |
0 commit comments