universal-data-cleaner 2.2.0__tar.gz → 2.4.0__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 (25) hide show
  1. {universal_data_cleaner-2.2.0/src/universal_data_cleaner.egg-info → universal_data_cleaner-2.4.0}/PKG-INFO +192 -2
  2. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/README.md +191 -1
  3. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/pyproject.toml +1 -1
  4. universal_data_cleaner-2.4.0/src/data_cleaner/__init__.py +86 -0
  5. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/_version.py +2 -2
  6. universal_data_cleaner-2.4.0/src/data_cleaner/artifacts.py +171 -0
  7. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/cleaner.py +336 -4
  8. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/config.py +1 -0
  9. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/contract.py +19 -0
  10. universal_data_cleaner-2.4.0/src/data_cleaner/decisions.py +50 -0
  11. universal_data_cleaner-2.4.0/src/data_cleaner/integration.py +613 -0
  12. universal_data_cleaner-2.4.0/src/data_cleaner/validation.py +581 -0
  13. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0/src/universal_data_cleaner.egg-info}/PKG-INFO +192 -2
  14. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/SOURCES.txt +6 -1
  15. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/tests/test_data_cleaner.py +241 -14
  16. universal_data_cleaner-2.4.0/tests/test_validation.py +399 -0
  17. universal_data_cleaner-2.2.0/src/data_cleaner/__init__.py +0 -37
  18. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/LICENSE +0 -0
  19. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/setup.cfg +0 -0
  20. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/_roles.py +0 -0
  21. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/profiling.py +0 -0
  22. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/py.typed +0 -0
  23. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/dependency_links.txt +0 -0
  24. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/requires.txt +0 -0
  25. {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-data-cleaner
3
- Version: 2.2.0
3
+ Version: 2.4.0
4
4
  Summary: Production-ready, scikit-learn-compatible data cleaning transformer for pandas.
5
5
  Author: Haseeb Khan
6
6
  License-Expression: MIT
@@ -241,6 +241,165 @@ restored = DataContract.from_json(payload)
241
241
  # restored = DataContract.from_yaml(contract.to_yaml())
242
242
  ```
243
243
 
244
+ ## Expectations, joins, and referential integrity
245
+
246
+ ### Expectations and validation reports
247
+
248
+ Run lightweight expectations against any batch and get a shareable report:
249
+
250
+ ```python
251
+ from data_cleaner import (
252
+ between,
253
+ has_dtype,
254
+ in_set,
255
+ matches_pattern,
256
+ not_null,
257
+ row_count_between,
258
+ unique,
259
+ validate,
260
+ )
261
+
262
+ report = validate(
263
+ orders_new,
264
+ [
265
+ not_null("order_id"),
266
+ unique("order_id"),
267
+ in_set("status", ["new", "paid", "shipped"]),
268
+ between("amount", 0, 100_000),
269
+ matches_pattern("email", r"[^@]+@[^@]+\.[^@]+"),
270
+ has_dtype("created_at", "datetime"),
271
+ row_count_between(min_rows=1),
272
+ ],
273
+ )
274
+ print(report.to_markdown())
275
+ report.raise_if_failed()
276
+ ```
277
+
278
+ `report.success` is true when no error-severity expectation failed.
279
+ `report.failures` includes warnings too, so use `severity="warning"` for soft
280
+ checks. `has_dtype` accepts broad kinds (`numeric`, `categorical`, `boolean`,
281
+ `datetime`) or an exact pandas dtype such as `float64`.
282
+
283
+ Contracts can validate a batch directly and return the same report type:
284
+
285
+ ```python
286
+ from data_cleaner import DataContract
287
+
288
+ contract = DataContract.from_frame(orders_train)
289
+ result = contract.validate(orders_new)
290
+ result.failures.to_markdown()
291
+ ```
292
+
293
+ `Contract` is a shorthand alias for `DataContract`.
294
+
295
+ ### Join diagnostics
296
+
297
+ ```python
298
+ from data_cleaner import diagnose_join
299
+
300
+ report = diagnose_join(orders, customers, on="customer_id", how="left")
301
+
302
+ report.safe # False on cardinality mismatch, left/right row multiplication, or null keys
303
+ report.cardinality # e.g. "1:m"
304
+ report.row_multiplier # output rows / left rows
305
+ report.unmatched_left
306
+ report.unmatched_right
307
+ report.warnings
308
+ report.raise_if_unsafe()
309
+ ```
310
+
311
+ `diagnose_join` defaults to `how="left"` and `validate="1:1"`. It reports
312
+ duplicate keys, null keys, unmatched rows, and the actual output row count
313
+ without materializing the full joined frame.
314
+
315
+ ### Referential integrity
316
+
317
+ ```python
318
+ from data_cleaner import check_referential_integrity
319
+
320
+ report = check_referential_integrity(
321
+ orders, customers, child_key="customer_id", parent_key="id"
322
+ )
323
+ report.orphan_rows
324
+ report.orphan_keys
325
+ report.raise_if_invalid()
326
+ ```
327
+
328
+ Null child keys are reported but allowed by default (`allow_null_keys=True`).
329
+ Duplicate parent keys make the relationship invalid because the parent key is
330
+ not a unique reference target.
331
+
332
+ ### Business-key deduplication
333
+
334
+ ```python
335
+ from data_cleaner import deduplicate_by_business_key
336
+
337
+ deduped, report = deduplicate_by_business_key(
338
+ orders, key="order_id", keep="last", tie_breaker="updated_at"
339
+ )
340
+ report.removed_rows
341
+ report.removed # the actual rows that were dropped
342
+ ```
343
+
344
+ The returned frame preserves the input row order and index. `tie_breaker`
345
+ prefers non-null values, and null business keys are reported explicitly.
346
+ The input frame is never mutated.
347
+
348
+ ## Audit trail, manifests, and fingerprints
349
+
350
+ Every learned or configured decision is recorded during `fit()` and exposed as
351
+ a tidy DataFrame:
352
+
353
+ ```python
354
+ log = cleaner.decision_log()
355
+ # columns: column, action, reason, source, before, after
356
+ log[log["action"] == "cap_outliers"]
357
+ ```
358
+
359
+ `source` says whether a rule was `learned` from the training data, supplied via
360
+ `config`, declared in a `contract`, or `inferred` by the library. Imputation
361
+ values, outlier bounds, datetime expansion, missing indicators, dropped
362
+ constants, and identifier handling are all explained instead of hidden.
363
+
364
+ Build a JSON-serializable manifest for logs or a model registry:
365
+
366
+ ```python
367
+ import json
368
+
369
+ manifest = cleaner.to_manifest(X_train, include_fingerprint=True)
370
+ # library_version, artifact_version, environment, config, contract,
371
+ # input_schema, output_columns, fitted, decisions, fingerprint
372
+
373
+ json.dumps(manifest) # safe to ship to a registry or a log line
374
+ ```
375
+
376
+ `save()` embeds the manifest and the training fingerprint in the artifact, and
377
+ `load()` restores them:
378
+
379
+ ```python
380
+ cleaner.save("cleaner.joblib")
381
+ restored = DataCleaner.load("cleaner.joblib")
382
+ restored.artifact_manifest()
383
+ ```
384
+
385
+ Fingerprints are stable SHA-256 hashes of the schema plus summary statistics.
386
+ They never hash raw values, so they are safe to log:
387
+
388
+ ```python
389
+ from data_cleaner import (
390
+ assert_schema_stable,
391
+ compare_fingerprints,
392
+ fingerprint_dataframe,
393
+ )
394
+
395
+ expected = fingerprint_dataframe(X_train)
396
+ same = compare_fingerprints(expected, fingerprint_dataframe(X_new))
397
+ assert_schema_stable(X_new, expected) # raises ValueError on schema/stat drift
398
+ ```
399
+
400
+ Set `CleanerConfig(fingerprint_on_fit=True)` to compute the training fingerprint
401
+ once during `fit()`, so `to_manifest(include_fingerprint=True)` needs no data.
402
+
244
403
  ## scikit-learn Pipeline
245
404
 
246
405
  ```python
@@ -279,11 +438,33 @@ predictions = pipeline.predict(X_test)
279
438
  | --- | --- |
280
439
  | `CleanerConfig` | Validated cleaning configuration |
281
440
  | `DataCleaner` | Fit/transform cleaner with save/load and drift reporting |
441
+ | `CleaningDecision` | One auditable learned or configured decision |
282
442
  | `ColumnSpec` | Declarative expectations and semantics for one column |
283
443
  | `DataContract` | A set of column specs, with inference and (de)serialization |
444
+ | `Contract` | Shorthand alias for `DataContract` |
445
+ | `DataContract.validate()` | Check a batch and return a `ValidationReport` |
284
446
  | `DriftReport` | Findings from a contract check, with a Markdown summary |
447
+ | `Expectation` | One reusable expectation produced by a factory |
448
+ | `validate` | Evaluate expectations against a frame |
449
+ | `not_null`, `unique`, `in_set`, `between` | Expectation factories |
450
+ | `matches_pattern`, `has_dtype` | Expectation factories |
451
+ | `row_count_between` | Frame-level row-count expectation |
452
+ | `ValidationReport` | Pass/fail results, failures table, Markdown, and raise helper |
453
+ | `diagnose_join` | Diagnose key duplication, cardinality, and row multiplication |
454
+ | `JoinDiagnosis` | Join report with multiplier, unmatched counts, and warnings |
455
+ | `check_referential_integrity` | Find orphan child keys and non-unique parent keys |
456
+ | `ReferentialIntegrityReport` | Orphan counts/keys, null keys, and validity |
457
+ | `deduplicate_by_business_key` | Deduplicate by key and report removed rows |
458
+ | `DeduplicationReport` | Removed rows, duplicate-key counts, and tie-breaker details |
285
459
  | `DataCleaner.drift_report()` | Drift findings from the last `transform` |
286
460
  | `DataCleaner.fitted_contract()` | The contract used by the fitted cleaner |
461
+ | `DataCleaner.decision_log()` | Tidy DataFrame of cleaning decisions |
462
+ | `DataCleaner.to_manifest()` | JSON-serializable artifact manifest |
463
+ | `DataCleaner.artifact_manifest()` | Manifest stored in a loaded artifact |
464
+ | `DataCleaner.fingerprint()` | Stable schema + statistics fingerprint |
465
+ | `fingerprint_dataframe` | Fingerprint any DataFrame for lineage |
466
+ | `compare_fingerprints` | Compare two fingerprints |
467
+ | `assert_schema_stable` | Raise when a frame's fingerprint changed |
287
468
  | `profile_dataframe` | Column-level missing/type/cardinality profile |
288
469
  | `dataset_summary` | Row/column/missing/duplicate summary |
289
470
  | `drop_duplicate_rows` | Explicit duplicate removal helper |
@@ -305,6 +486,10 @@ predictions = pipeline.predict(X_test)
305
486
  represented by the generated feature NaNs plus an optional missing indicator.
306
487
  - IQR capping is skipped automatically when the IQR is zero, so constant or
307
488
  near-constant numeric columns are not accidentally destroyed.
489
+ - Persist `cleaner.to_manifest(X_train, include_fingerprint=True)` alongside the
490
+ model so you can audit exactly which cleaning rules produced an artifact.
491
+ - `decision_log()` explains every learned or configured rule; ship it to your
492
+ experiment tracker when a model's preprocessing changes.
308
493
 
309
494
  ## Project layout
310
495
 
@@ -314,13 +499,18 @@ predictions = pipeline.predict(X_test)
314
499
  │ ├── __init__.py # public API re-exports
315
500
  │ ├── _version.py # version metadata
316
501
  │ ├── _roles.py # semantic roles and dtype helpers
502
+ │ ├── artifacts.py # fingerprints and runtime metadata
317
503
  │ ├── contract.py # ColumnSpec / DataContract / DriftReport
318
504
  │ ├── config.py # CleanerConfig
319
505
  │ ├── cleaner.py # DataCleaner
506
+ │ ├── decisions.py # CleaningDecision and decision log
507
+ │ ├── integration.py # joins, referential integrity, dedup
320
508
  │ ├── profiling.py # profiling and validation helpers
509
+ │ ├── validation.py # expectations and ValidationReport
321
510
  │ └── py.typed # PEP 561 marker
322
511
  ├── Universal_Data_Cleaner.ipynb # end-to-end reference workflow
323
- ├── tests/test_data_cleaner.py # unit and integration tests
512
+ ├── tests/test_data_cleaner.py # core unit and integration tests
513
+ ├── tests/test_validation.py # expectations and integration tests
324
514
  ├── pyproject.toml # package metadata and dev tooling
325
515
  ├── uv.lock # reproducible dependency lock
326
516
  ├── .pre-commit-config.yaml # local lint/format/type hooks
@@ -208,6 +208,165 @@ restored = DataContract.from_json(payload)
208
208
  # restored = DataContract.from_yaml(contract.to_yaml())
209
209
  ```
210
210
 
211
+ ## Expectations, joins, and referential integrity
212
+
213
+ ### Expectations and validation reports
214
+
215
+ Run lightweight expectations against any batch and get a shareable report:
216
+
217
+ ```python
218
+ from data_cleaner import (
219
+ between,
220
+ has_dtype,
221
+ in_set,
222
+ matches_pattern,
223
+ not_null,
224
+ row_count_between,
225
+ unique,
226
+ validate,
227
+ )
228
+
229
+ report = validate(
230
+ orders_new,
231
+ [
232
+ not_null("order_id"),
233
+ unique("order_id"),
234
+ in_set("status", ["new", "paid", "shipped"]),
235
+ between("amount", 0, 100_000),
236
+ matches_pattern("email", r"[^@]+@[^@]+\.[^@]+"),
237
+ has_dtype("created_at", "datetime"),
238
+ row_count_between(min_rows=1),
239
+ ],
240
+ )
241
+ print(report.to_markdown())
242
+ report.raise_if_failed()
243
+ ```
244
+
245
+ `report.success` is true when no error-severity expectation failed.
246
+ `report.failures` includes warnings too, so use `severity="warning"` for soft
247
+ checks. `has_dtype` accepts broad kinds (`numeric`, `categorical`, `boolean`,
248
+ `datetime`) or an exact pandas dtype such as `float64`.
249
+
250
+ Contracts can validate a batch directly and return the same report type:
251
+
252
+ ```python
253
+ from data_cleaner import DataContract
254
+
255
+ contract = DataContract.from_frame(orders_train)
256
+ result = contract.validate(orders_new)
257
+ result.failures.to_markdown()
258
+ ```
259
+
260
+ `Contract` is a shorthand alias for `DataContract`.
261
+
262
+ ### Join diagnostics
263
+
264
+ ```python
265
+ from data_cleaner import diagnose_join
266
+
267
+ report = diagnose_join(orders, customers, on="customer_id", how="left")
268
+
269
+ report.safe # False on cardinality mismatch, left/right row multiplication, or null keys
270
+ report.cardinality # e.g. "1:m"
271
+ report.row_multiplier # output rows / left rows
272
+ report.unmatched_left
273
+ report.unmatched_right
274
+ report.warnings
275
+ report.raise_if_unsafe()
276
+ ```
277
+
278
+ `diagnose_join` defaults to `how="left"` and `validate="1:1"`. It reports
279
+ duplicate keys, null keys, unmatched rows, and the actual output row count
280
+ without materializing the full joined frame.
281
+
282
+ ### Referential integrity
283
+
284
+ ```python
285
+ from data_cleaner import check_referential_integrity
286
+
287
+ report = check_referential_integrity(
288
+ orders, customers, child_key="customer_id", parent_key="id"
289
+ )
290
+ report.orphan_rows
291
+ report.orphan_keys
292
+ report.raise_if_invalid()
293
+ ```
294
+
295
+ Null child keys are reported but allowed by default (`allow_null_keys=True`).
296
+ Duplicate parent keys make the relationship invalid because the parent key is
297
+ not a unique reference target.
298
+
299
+ ### Business-key deduplication
300
+
301
+ ```python
302
+ from data_cleaner import deduplicate_by_business_key
303
+
304
+ deduped, report = deduplicate_by_business_key(
305
+ orders, key="order_id", keep="last", tie_breaker="updated_at"
306
+ )
307
+ report.removed_rows
308
+ report.removed # the actual rows that were dropped
309
+ ```
310
+
311
+ The returned frame preserves the input row order and index. `tie_breaker`
312
+ prefers non-null values, and null business keys are reported explicitly.
313
+ The input frame is never mutated.
314
+
315
+ ## Audit trail, manifests, and fingerprints
316
+
317
+ Every learned or configured decision is recorded during `fit()` and exposed as
318
+ a tidy DataFrame:
319
+
320
+ ```python
321
+ log = cleaner.decision_log()
322
+ # columns: column, action, reason, source, before, after
323
+ log[log["action"] == "cap_outliers"]
324
+ ```
325
+
326
+ `source` says whether a rule was `learned` from the training data, supplied via
327
+ `config`, declared in a `contract`, or `inferred` by the library. Imputation
328
+ values, outlier bounds, datetime expansion, missing indicators, dropped
329
+ constants, and identifier handling are all explained instead of hidden.
330
+
331
+ Build a JSON-serializable manifest for logs or a model registry:
332
+
333
+ ```python
334
+ import json
335
+
336
+ manifest = cleaner.to_manifest(X_train, include_fingerprint=True)
337
+ # library_version, artifact_version, environment, config, contract,
338
+ # input_schema, output_columns, fitted, decisions, fingerprint
339
+
340
+ json.dumps(manifest) # safe to ship to a registry or a log line
341
+ ```
342
+
343
+ `save()` embeds the manifest and the training fingerprint in the artifact, and
344
+ `load()` restores them:
345
+
346
+ ```python
347
+ cleaner.save("cleaner.joblib")
348
+ restored = DataCleaner.load("cleaner.joblib")
349
+ restored.artifact_manifest()
350
+ ```
351
+
352
+ Fingerprints are stable SHA-256 hashes of the schema plus summary statistics.
353
+ They never hash raw values, so they are safe to log:
354
+
355
+ ```python
356
+ from data_cleaner import (
357
+ assert_schema_stable,
358
+ compare_fingerprints,
359
+ fingerprint_dataframe,
360
+ )
361
+
362
+ expected = fingerprint_dataframe(X_train)
363
+ same = compare_fingerprints(expected, fingerprint_dataframe(X_new))
364
+ assert_schema_stable(X_new, expected) # raises ValueError on schema/stat drift
365
+ ```
366
+
367
+ Set `CleanerConfig(fingerprint_on_fit=True)` to compute the training fingerprint
368
+ once during `fit()`, so `to_manifest(include_fingerprint=True)` needs no data.
369
+
211
370
  ## scikit-learn Pipeline
212
371
 
213
372
  ```python
@@ -246,11 +405,33 @@ predictions = pipeline.predict(X_test)
246
405
  | --- | --- |
247
406
  | `CleanerConfig` | Validated cleaning configuration |
248
407
  | `DataCleaner` | Fit/transform cleaner with save/load and drift reporting |
408
+ | `CleaningDecision` | One auditable learned or configured decision |
249
409
  | `ColumnSpec` | Declarative expectations and semantics for one column |
250
410
  | `DataContract` | A set of column specs, with inference and (de)serialization |
411
+ | `Contract` | Shorthand alias for `DataContract` |
412
+ | `DataContract.validate()` | Check a batch and return a `ValidationReport` |
251
413
  | `DriftReport` | Findings from a contract check, with a Markdown summary |
414
+ | `Expectation` | One reusable expectation produced by a factory |
415
+ | `validate` | Evaluate expectations against a frame |
416
+ | `not_null`, `unique`, `in_set`, `between` | Expectation factories |
417
+ | `matches_pattern`, `has_dtype` | Expectation factories |
418
+ | `row_count_between` | Frame-level row-count expectation |
419
+ | `ValidationReport` | Pass/fail results, failures table, Markdown, and raise helper |
420
+ | `diagnose_join` | Diagnose key duplication, cardinality, and row multiplication |
421
+ | `JoinDiagnosis` | Join report with multiplier, unmatched counts, and warnings |
422
+ | `check_referential_integrity` | Find orphan child keys and non-unique parent keys |
423
+ | `ReferentialIntegrityReport` | Orphan counts/keys, null keys, and validity |
424
+ | `deduplicate_by_business_key` | Deduplicate by key and report removed rows |
425
+ | `DeduplicationReport` | Removed rows, duplicate-key counts, and tie-breaker details |
252
426
  | `DataCleaner.drift_report()` | Drift findings from the last `transform` |
253
427
  | `DataCleaner.fitted_contract()` | The contract used by the fitted cleaner |
428
+ | `DataCleaner.decision_log()` | Tidy DataFrame of cleaning decisions |
429
+ | `DataCleaner.to_manifest()` | JSON-serializable artifact manifest |
430
+ | `DataCleaner.artifact_manifest()` | Manifest stored in a loaded artifact |
431
+ | `DataCleaner.fingerprint()` | Stable schema + statistics fingerprint |
432
+ | `fingerprint_dataframe` | Fingerprint any DataFrame for lineage |
433
+ | `compare_fingerprints` | Compare two fingerprints |
434
+ | `assert_schema_stable` | Raise when a frame's fingerprint changed |
254
435
  | `profile_dataframe` | Column-level missing/type/cardinality profile |
255
436
  | `dataset_summary` | Row/column/missing/duplicate summary |
256
437
  | `drop_duplicate_rows` | Explicit duplicate removal helper |
@@ -272,6 +453,10 @@ predictions = pipeline.predict(X_test)
272
453
  represented by the generated feature NaNs plus an optional missing indicator.
273
454
  - IQR capping is skipped automatically when the IQR is zero, so constant or
274
455
  near-constant numeric columns are not accidentally destroyed.
456
+ - Persist `cleaner.to_manifest(X_train, include_fingerprint=True)` alongside the
457
+ model so you can audit exactly which cleaning rules produced an artifact.
458
+ - `decision_log()` explains every learned or configured rule; ship it to your
459
+ experiment tracker when a model's preprocessing changes.
275
460
 
276
461
  ## Project layout
277
462
 
@@ -281,13 +466,18 @@ predictions = pipeline.predict(X_test)
281
466
  │ ├── __init__.py # public API re-exports
282
467
  │ ├── _version.py # version metadata
283
468
  │ ├── _roles.py # semantic roles and dtype helpers
469
+ │ ├── artifacts.py # fingerprints and runtime metadata
284
470
  │ ├── contract.py # ColumnSpec / DataContract / DriftReport
285
471
  │ ├── config.py # CleanerConfig
286
472
  │ ├── cleaner.py # DataCleaner
473
+ │ ├── decisions.py # CleaningDecision and decision log
474
+ │ ├── integration.py # joins, referential integrity, dedup
287
475
  │ ├── profiling.py # profiling and validation helpers
476
+ │ ├── validation.py # expectations and ValidationReport
288
477
  │ └── py.typed # PEP 561 marker
289
478
  ├── Universal_Data_Cleaner.ipynb # end-to-end reference workflow
290
- ├── tests/test_data_cleaner.py # unit and integration tests
479
+ ├── tests/test_data_cleaner.py # core unit and integration tests
480
+ ├── tests/test_validation.py # expectations and integration tests
291
481
  ├── pyproject.toml # package metadata and dev tooling
292
482
  ├── uv.lock # reproducible dependency lock
293
483
  ├── .pre-commit-config.yaml # local lint/format/type hooks
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "universal-data-cleaner"
7
- version = "2.2.0"
7
+ version = "2.4.0"
8
8
  description = "Production-ready, scikit-learn-compatible data cleaning transformer for pandas."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,86 @@
1
+ """Universal Data Cleaner — production-oriented tabular cleaning toolkit.
2
+
3
+ The package is intentionally dependency-light:
4
+
5
+ - pandas + numpy for data handling
6
+ - scikit-learn base classes for Pipeline / clone compatibility
7
+ - joblib for artifact persistence
8
+
9
+ The public API is re-exported here so ``import data_cleaner`` keeps working
10
+ exactly as it did when the library shipped as a single module.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from ._version import __version__
16
+ from .artifacts import (
17
+ assert_schema_stable,
18
+ compare_fingerprints,
19
+ fingerprint_dataframe,
20
+ )
21
+ from .cleaner import DataCleaner
22
+ from .config import CleanerConfig
23
+ from .contract import ColumnSpec, Contract, DataContract, DriftReport
24
+ from .decisions import CleaningDecision
25
+ from .integration import (
26
+ DeduplicationReport,
27
+ JoinDiagnosis,
28
+ ReferentialIntegrityReport,
29
+ check_referential_integrity,
30
+ deduplicate_by_business_key,
31
+ diagnose_join,
32
+ )
33
+ from .profiling import (
34
+ assert_frame_clean,
35
+ dataset_summary,
36
+ drop_duplicate_rows,
37
+ profile_dataframe,
38
+ )
39
+ from .validation import (
40
+ Expectation,
41
+ ValidationReport,
42
+ ValidationResult,
43
+ between,
44
+ has_dtype,
45
+ in_set,
46
+ matches_pattern,
47
+ not_null,
48
+ row_count_between,
49
+ unique,
50
+ validate,
51
+ )
52
+
53
+ __all__ = [
54
+ "CleanerConfig",
55
+ "CleaningDecision",
56
+ "ColumnSpec",
57
+ "Contract",
58
+ "DataCleaner",
59
+ "DataContract",
60
+ "DeduplicationReport",
61
+ "DriftReport",
62
+ "Expectation",
63
+ "JoinDiagnosis",
64
+ "ReferentialIntegrityReport",
65
+ "ValidationReport",
66
+ "ValidationResult",
67
+ "__version__",
68
+ "assert_frame_clean",
69
+ "assert_schema_stable",
70
+ "between",
71
+ "check_referential_integrity",
72
+ "compare_fingerprints",
73
+ "dataset_summary",
74
+ "deduplicate_by_business_key",
75
+ "diagnose_join",
76
+ "drop_duplicate_rows",
77
+ "fingerprint_dataframe",
78
+ "has_dtype",
79
+ "in_set",
80
+ "matches_pattern",
81
+ "not_null",
82
+ "profile_dataframe",
83
+ "row_count_between",
84
+ "unique",
85
+ "validate",
86
+ ]
@@ -1,4 +1,4 @@
1
1
  """Single source of truth for the package version."""
2
2
 
3
- __version__ = "2.2.0"
4
- _ARTIFACT_VERSION = 1
3
+ __version__ = "2.4.0"
4
+ _ARTIFACT_VERSION = 2