Changelog¶
All notable changes to this project will be documented in this file.
Unreleased¶
Fixed¶
- String checks ignore null values consistently, including pandas object columns with newer Narwhals versions.
3.1.0¶
A six-angle audit of the codebase found four bugs where validation silently did not happen or crashed outside daffy's error contract. Everything here either closes one of those, or makes a failure say what went wrong.
Migrating: column specs that were quietly doing nothing now raise when the decorator is
applied — a misspelled constraint ({"nullabel": False}), a non-boolean nullable/unique/
required, or a check whose dtype it cannot mean anything on. Each one was a guard that
never ran, so the error is reporting a bug you already had.
Fixed¶
- Overlapping column specs no longer drop a constraint.
{"r/^price/": {"checks": {"gt": 0}}, "price_eur": {"checks": {"lt": 100}}}applied only one of the two checks, and which one depended on dict insertion order. Every spec that matches a column now applies. The same overlap crashednullableanduniquewith a NarwhalsDuplicateErrorrather than anAssertionError. strict=Trueis no longer ignored when the column spec is empty.@df_in([], strict=True)permitted any frame; an empty spec now means no columns are permitted.- Duplicate column names no longer escape as a Narwhals error.
@df_in/@df_outreport them as anAssertionError, and@df_log— which only logs — warns and leaves the function working instead of raising. - A check that cannot mean anything on a column's dtype is now rejected on every backend. An ordering check on a string column, or a
str_*check on a non-string column, previously raised a raw backend error on Pandas and PyArrow while Polars compared lexicographically and reported a violation that looked real. Other backend failures during a check are wrapped with the check name, dtype and value. - Row validation no longer understates catastrophic failures. With early termination, 10,000 failing rows out of 10,000 reported "at least 6 … and 1 more row(s)". It now says counting stopped rather than inventing a remainder.
- The decorators no longer erase type information. They returned
Callable[..., T], which silently disabled argument type checking at every call site of a decorated function.ColumnsDefalso rejected any spec that was not an inline literal, includingdict[str, ColumnConstraints]. - Errors past a
*argsboundary named the wrong parameter, and wrong-type errors did not name the function.
Changed¶
- Unusable column specs are rejected when the decorator is applied. Misspelled constraint names, non-boolean
nullable/unique/requiredvalues, and misspelled built-in check names all used to be accepted and then do nothing. Custom checks are unaffected: a callable check value may carry any name.
Performance¶
- Another ~7% off per-call overhead (
@df_in(columns=...)~41µs → ~38µs, measured on a 100-row Pandas frame) by removingSkippableValidator, a protocol with no implementations that cost aruntime_checkableisinstanceper validator per call. Total since 2.8.0 is ~212µs → ~38µs.
Removed¶
daffy.validators.SkippableValidatorand theshould_skiphook. Nothing in daffy implemented it, andValidationPipelineno longer calls it. It is removed rather than deprecated deliberately: a compatibility shim would keepfrom daffy.validators import SkippableValidatorworking while silently never skipping anything, which is the failure mode this release exists to remove. Validators already express "nothing to do" by returning[], and the builder omits validators with no work.
Documentation¶
- Corrected five quoted error/log outputs that no longer matched what daffy prints, fixed three README documentation links that 404'd, and documented
strict_specs, the three config keys missing from the API reference,ColumnConstraints, and the fact that config discovery is relative to the working directory.
3.0.0¶
This release removes two pieces of hidden magic and makes validation roughly five times cheaper. Mutation testing found all three behavior changes: the test suite asserted none of them, and the documentation described none of them.
Migrating: anchor any unanchored regex you relied on (r"\d+" → r"^\d+", r/Price_\d+/ →
r/^Price_\d+/), and add nullable=False to columns where you were relying on a null failing a
value check.
Changed¶
- Behavior change: value checks no longer force nulls to count as failures. Daffy used to rewrite null comparison results into violations; it now leaves each backend's semantics intact, so on Polars, PyArrow and Pandas nullable dtypes a comparison against null is "unknown" and is not reported. Pandas float
NaNcompares asFalseand still fails. Usenullable=Falseor thenotnullcheck to constrain nulls explicitly — those behave identically on every backend. - Behavior change: regex column patterns (
"r/pattern/") now match anywhere in the column name instead of only at the start, sor/Price_\d+/also matchesTotal_Price_1. Anchor the pattern yourself (r/^Price_\d+/) to keep the old meaning. This makes column patterns andstr_regexfollow the same rule. - Behavior change:
str_regexnow applies the pattern as written instead of silently anchoring it at the start of the value.{"str_regex": r"\d+"}used to mean "starts with digits" and now means "contains digits"; add the anchor yourself (r"^\d+", orr"^\d+$"for a full match) to keep the old meaning. Patterns that were already anchored are unaffected.
Fixed¶
- Parameterised dtypes can now be declared by base name:
{"created_at": "datetime"}matches any time unit or time zone, and the same applies toduration,list,structandenum. Previously the only accepted spelling was the full internal repr ("datetime(time_unit='ns', time_zone=none)"). Spelling the parameters out still constrains them. - String checks (
str_regex,str_startswith,str_endswith,str_contains) no longer raiseTypeError: bad operand type for unary ~on Pandas object-dtype columns containing nulls
Performance¶
- Per-call validation overhead cut by roughly 5x:
@df_in(columns=...)on a 100-row Pandas frame went from ~212µs to ~41µs,@df_in(min_rows=...)from ~216µs to ~44µs. Configuration is now resolved in one lookup per call instead of one per setting, the DataFrame is converted to Narwhals once instead of three times, and column dtypes are only read when a dtype constraint needs them.
Removed¶
daffy.config.get_strict,get_lazy,get_strict_specsandget_allow_empty— internal helpers superseded byresolve_decorator_settings. They were never exported from thedaffypackage.
Documentation¶
- Documented null handling per backend,
str_regexanchoring, how to declare parameterised dtypes, and that regex syntax support depends on the backend's own engine
2.8.0¶
Changed¶
- Significant runtime performance improvements to minimize decorator overhead
- Eliminated repetitive Python introspection (
inspect.signature) from the execution path by resolving parameters during decoration - Vectorized validation checks (
nullableandunique) across multiple columns simultaneously using Narwhals expression API (.select()) - Avoided redundant dataframe wrappers when running value checks
- Optimized the validation builder for faster pipeline setup and eliminated unused dtype resolution overhead
2.7.0¶
Added¶
@df_in(["col1", "col2"])shorthand: pass columns as the first positional argument- Opt-in
strict_specsmode ([tool.daffy] strict_specs = true): raisesTypeErroron invalid column spec keys or types instead of silently ignoring them
Changed¶
- Config discovery now searches
pyproject.tomlfrom the current directory up through parent directories - Configuration caching is now keyed by current working directory for deterministic behavior across cwd changes
- Unnamed
df_innow selects the first DataFrame-like argument instead of always taking the first positional argument
2.6.1¶
Changed¶
- Optional dependency detection now recognizes all advertised backends (Pandas, Polars, Modin, PyArrow)
- Early import failure now triggers only when none of the supported DataFrame libraries are installed
- Updated optional dependency tests and isolation docs/scripts to align with the expanded backend detection contract
- Synced API docs and README examples with current
df_in/df_out/df_logsignatures and built-in check names
Added¶
- Declared
pydanticoptional dependency extra sopip install 'daffy[pydantic]'matches runtime install guidance - Added docs contract tests to detect signature/example drift in
README.mdanddocs/api.md
2.6.0¶
Added¶
- Support for pandas 3.x (requires Python 3.11+)
2.5.1¶
Internal Improvements¶
- Simplified config loading by using loops over key lists instead of repetitive if-statements
- Added
_get_int_confighelper to deduplicate integer config getters - Extracted
_log_dataframehelper from duplicate logging functions - Replaced repeated expand+add blocks in pipeline builder with data-driven loop
2.5.0¶
Internal Improvements¶
- Major architecture refactoring: Introduced validation pipeline with protocol-based validators
- New
ValidationPipelineorchestrates all validation steps - New
ValidationContextprovides single narwhals conversion point for efficiency - Validators follow a clean protocol (
Validator,SkippableValidator) - Pipeline builder assembles validators from column specs
- Removed dead code and unused helper functions
- Removed unused type aliases (
ColumnsList,ColumnsDict) - Replaced skylos with vulture for dead code detection in CI
2.4.1¶
Internal Improvements¶
- Enabled ALL Ruff linting rules with selective ignores for comprehensive code quality checks
- Refactored
validate_dataframeto reduce cyclomatic complexity - Migrated from
os.pathtopathlibin configuration module - Performance optimization: extracted try/except from validation loop (PERF203)
2.4.0¶
New Features¶
- Shape validation: enforce row count constraints on DataFrames
min_rows: require a minimum number of rowsmax_rows: limit the maximum number of rowsexact_rows: require an exact number of rowsallow_empty: control whether empty DataFrames (0 rows) are allowed- New
allow_emptyconfiguration option inpyproject.toml(default:true)
Internal Improvements¶
- Added property-based tests with Hypothesis for mathematical invariants
- Added boundary-value and default-parameter tests for improved test coverage
2.3.1¶
Bug Fixes¶
- Fixed incomplete
DataFrameTypeunion that only included Pandas and Polars, missing PyArrow and Modin. Now uses narwhals'IntoDataFrameProtocol which correctly accepts all supported DataFrame types.
2.3.0¶
Breaking Changes¶
- Dropped Python 3.9 support - Python 3.10 is now the minimum required version (Python 3.9 reached end-of-life in October 2025)
Internal Improvements¶
- Modernized type annotations using Python 3.10 syntax (
X | Yinstead ofUnion[X, Y], explicitTypeAlias) - Updated ruff target version to Python 3.10
2.2.0¶
New Features¶
- Custom check functions: use lambdas for validation logic not covered by built-in checks
- The function receives a Narwhals Series and returns a boolean Series (True = valid)
- Example:
{"price": {"checks": {"no_outliers": lambda s: s < s.mean() * 10}}} - The dictionary key becomes the check name in error messages
- Lazy validation: collect all errors before raising with
lazy=Trueparameter - Use
@df_in(columns=..., lazy=True)or@df_out(columns=..., lazy=True) - Configurable via
[tool.daffy] lazy = truein pyproject.toml - Useful for debugging DataFrames with multiple issues
- Composite uniqueness: validate that column combinations are unique
- Use
composite_unique=[["col1", "col2"]]to ensure column combinations are unique - Works alongside single-column
unique=Truevalidation
Bug Fixes¶
- Fixed value checks (
gt,lt,ge,le,between,eq,ne, etc.) not working with PyArrow tables
Internal Improvements¶
- Config validation: pyproject.toml settings now validated for correct types (prevents silent bugs like
strict = "false"being treated as truthy) - Regex pattern caching: compiled patterns are now cached, improving performance when validating many DataFrames with the same column patterns
- Empty regex patterns (
r//) now raise a clear error instead of matching everything - Improved error handling for custom check functions
2.1.0¶
New Features¶
- Added new value checks:
notin,str_startswith,str_endswith,str_contains,str_length
2.0.2¶
Documentation¶
- Released MkDocs documentation at daffy.readthedocs.io
- Added missing
attr_list,md_in_html, andpymdownx.emojimarkdown extensions - Improved SEO: updated README title/intro, added PyPI keywords, added GitHub topics
2.0.1¶
Documentation¶
- Simplified installation instructions - Daffy detects and works with whatever DataFrame library you have installed
- Updated PyPI keywords to include modin, pyarrow, and narwhals
- Added note that examples work with all supported backends (Pandas, Polars, Modin, PyArrow)
2.0.0¶
Major Refactoring¶
- Migrated to Narwhals - Major internal refactoring to use Narwhals as a unified DataFrame abstraction layer
- Narwhals is a lightweight compatibility layer used by Plotly, Altair, Bokeh, and Marimo
- Now supports Modin and PyArrow in addition to Pandas and Polars
- All existing functionality for Pandas and Polars remains unchanged
- Public API (
df_in,df_out,df_log) is fully backwards compatible
Changed¶
df_logwithinclude_dtypes=Truenow shows unified Narwhals dtype representation (e.g.,[String, Int64]) for both Pandas and Polars DataFrames- Previously Pandas showed
['object', 'int64']while Polars showed[String, Int64] - This provides consistent logging output across all DataFrame libraries
Dependencies¶
- Added
narwhals>=2.14.0as a required dependency
Removed¶
row_validation_convert_nansconfiguration option - NaN-to-None conversion is no longer needed. Pydantic handles NaN values correctly: accepts them forOptional[float]fields and correctly fails for constraints likeField(gt=0)
1.4.0¶
New Features¶
- Vectorized value checks - Fast validation of column values using vectorized DataFrame operations
- Comparison checks:
gt,ge,lt,le(greater than, greater or equal, less than, less or equal) - Range check:
betweenfor inclusive range validation - Equality checks:
eq,nefor exact value matching - Set membership:
isinfor validating values against a list - Null check:
notnullfor ensuring no null values - Regex matching:
str_regexfor pattern validation on string columns - Use rich column spec format:
{"price": {"checks": {"gt": 0, "lt": 10000}}} - Combine multiple checks:
{"age": {"checks": {"ge": 0, "le": 120}}} - Works with other validations:
{"price": {"dtype": "float64", "nullable": False, "checks": {"gt": 0}}} - Supports regex column patterns:
{"r/score_\\d+/": {"checks": {"between": (0, 100)}}} - Checks are skipped for missing optional columns
- Error messages include sample failing values for debugging
- Configurable via
checks_max_samplesin pyproject.toml
1.3.0¶
New Features¶
- Optional columns - Mark columns as optional with
required=False - Use rich column spec format:
{"column": {"required": False}} - Optional columns are not required to exist in the DataFrame
- If present, all other validations (dtype, nullable, unique) still apply
- Works with regex patterns:
{"r/extra_\\d+/": {"required": False}} - Default behavior unchanged (required=True, columns must exist)
1.2.0¶
New Features¶
- Uniqueness column validation - Validate that columns contain only unique values
- Use rich column spec format:
{"column": {"unique": True}} - Combines with dtype and nullable:
{"column": {"dtype": "int64", "unique": True, "nullable": False}} - Works with regex patterns:
{"r/ID_\\d+/": {"unique": True}} - Default behavior unchanged (unique=False, duplicates allowed)
1.1.0¶
New Features¶
- Nullable column validation - Validate that columns contain no null values
- Use rich column spec format:
{"column": {"nullable": False}} - Combines with dtype validation:
{"column": {"dtype": "float64", "nullable": False}} - Works with regex patterns:
{"r/Price_\\d+/": {"nullable": False}} - Default behavior unchanged (nullable=True, nulls allowed)
1.0.0¶
Stable Release¶
Daffy 1.0.0 marks the first stable release. The public API (df_in, df_out, df_log) is now considered stable and follows semantic versioning.
Changed¶
- Development status upgraded from Beta to Production/Stable
- Updated documentation to reflect current tooling (uv, ruff, pyrefly)
Fixed¶
- Improved error handling for invalid regex patterns in column specifications
- Better error messages when parameter extraction fails
Internal¶
- Extracted duplicate row validation logic into shared helper function
- Added docstrings to public-facing utility functions
API Stability¶
As of 1.0.0, Daffy follows semantic versioning:
- Major versions (2.0, 3.0) may contain breaking changes
- Minor versions (1.1, 1.2) add features without breaking changes
- Patch versions (1.0.1, 1.0.2) contain bug fixes only
0.19.0¶
Performance Improvements¶
- Early termination for row validation - Dramatically faster when validation errors exist
- Stops scanning after collecting
max_errors(default behavior) - 71-124x speedup when errors are present (1.2ms vs 140ms for 100k rows with errors)
- No overhead for valid data (maintains 767K rows/sec throughput)
- Can be disabled with
early_termination=Falseparameter for exact error counts - Error messages now indicate when scanning stopped early: "stopped scanning early (at least N more row(s) with errors)"
New Features¶
- Added
early_terminationparameter tovalidate_dataframe_rows() - Default:
True(stops aftermax_errorsfor performance) - Set to
Falseto scan entire DataFrame and get exact error count
0.18.0¶
Major Performance Improvements¶
- 2x faster row validation - Optimized DataFrame conversion and validation pipeline
- 767K rows/sec on simple validation (was 400K)
- 165K rows/sec on complex bioinformatics data (32 columns, 5% missing values)
- Changed from batch TypeAdapter validation to optimized row-by-row with fast DataFrame iteration
- Use
itertuples()for efficient row access while preserving None values
Critical Bug Fix¶
- Fixed NaN handling for Optional fields - NaN values in Optional fields are now properly converted to None
- Previous implementation failed validation on legitimate missing data
- Now correctly handles nullable numeric fields in pandas DataFrames
- Converts numeric columns with NaN to object dtype to preserve None values
New Features¶
- Bioinformatics benchmark (
scripts/benchmark_bioinformatics.py) - Realistic validation testing - 32-column feature store schema modeling cancer research data
- Gene expression, clinical measurements, patient demographics, mutations, outcomes
- Mixed types: floats, ints, strings, bools, Optional fields, Literal enums
- Missing data patterns (~5%) typical in real-world datasets
-
Cross-field validation (e.g., disease-free survival ≤ follow-up time)
-
Performance benchmarking suite - Compare Daffy against competing libraries
- Test multiple scenarios (simple, medium complexity)
- Multiple dataset sizes (1k, 10k, 100k rows)
- Compare pandas vs polars implementations
Internal Improvements¶
- Removed unused internal functions (
_pandas_to_records_fast,_iterate_dataframe_with_index) - Simplified error formatting (removed dead code branches)
- Improved test coverage to 98.34%
- Better handling of edge cases in validation error reporting
0.17.1¶
- Improve package discoverability on PyPI and GitHub
- Add polars, pydantic, decorator to PyPI keywords
- Add Framework::Pydantic, Testing, and Typing classifiers
- Add changelog and issues URLs to package metadata
- Set GitHub topics for better search visibility
0.17.0¶
- Add optional row-level validation using Pydantic models (requires Pydantic >= 2.4.0)
- New
row_validatorparameter for@df_inand@df_outdecorators - Validates actual data values, not just column structure
- Batch validation for optimal performance (10-100x faster than row-by-row)
- Informative error messages showing which rows failed and why
- Configuration via
pyproject.toml:row_validation_max_errorsandrow_validation_convert_nans - Works with both Pandas and Polars DataFrames
0.16.1¶
- Internal refactoring: extracted DataFrame type handling to dedicated module for better code organization and maintainability
0.16.0¶
- Removed Pandas and Polars from required dependencies. Daffy will not pull in Polars if your project just uses Pandas and vice versa. All combinations are dynamically supported and require no changes from existing users.
Testing & CI¶
- Added comprehensive CI testing for all dependency combinations
- New test suite validates optional dependency behavior
- Manual testing script for developers (
scripts/test_isolated_deps.py) - Updated CI to test pandas-only, polars-only, both, and none scenarios
0.15.0¶
- Exception messages now include function names to improve debugging
- Input validation:
"Missing columns: ['Col'] in function 'my_func' parameter 'param'. Got columns: ['Other']" - Return value validation messages now clearly state "return value" instead of just showing function name
- Output validation:
"Missing columns: ['Col'] in function 'my_func' return value. Got columns: ['Other']"
0.14.2¶
- Internal code quality improvements
0.14.1¶
- Internal code quality improvements
0.14.0¶
- Improve df_in error messages to include parameter names
0.13.2¶
- Updated urls for Pypi site compatibility
0.13.1¶
- Update documentation
- Update dependencies
0.13.0¶
- Fix type annotation issues with decorator parameters that could cause type errors in strict type checking
- Use
Sequenceinstead ofListfor better type variance compatibility - Add test case that validates type compatibility
0.12.0¶
- Add support for regex patterns used with column dtype validation
0.11.0¶
- Update function parameter types for better type safety
- Fix missing return statement in df_log decorator
- Added stricter mypy type checking settings
0.10.1¶
- Built and published with UV. No functional changes
0.10.0¶
- Add support for regex patterns in column name validation
0.9.4¶
- Fix to strict flag loading when tool config was missing
0.9.3¶
- Add configuration system to set default strict mode in pyproject.toml
- Improve logging when multiple columns are missing
0.9.2¶
- Add explicit
__all__export for functions to make Mypy happy
0.9.0¶
- Add marker (
py.typed) to tell Mypy that the library has type annotations - Fix bug when using
strictparameter and nonameparameter in@df_in
0.8.0¶
- Support Polars DataFrames
0.7.0¶
- Support Pandas 2.x
- Drop support for Python 3.7 and 3.8
- Build and test with Python 3.12 also
0.6.0¶
- Make checking columns of multiple function parameters work also with positional arguments (thanks @latvanii)
0.5.0¶
- Added
strictparameter for@df_inand@df_out
0.4.2¶
- Added docstrings for the decorators
- Fix import of
@df_log
0.4.1¶
- Add
include_dtypesparameter for@df_log. - Fix handling of empty signature with
@df_in.
0.4.0¶
- Added
@df_logfor logging. - Improved assertion messages.
0.3.0¶
- Added type hints.
0.2.1¶
- Added Pypi classifiers.
0.2.0¶
- Fixed decorator usage.
- Added functools wraps.
0.1.0¶
- Initial release.