dataframely 2.11.0__tar.gz → 2.13.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.
- {dataframely-2.11.0 → dataframely-2.13.0}/PKG-INFO +1 -1
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_polars.py +14 -5
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/collection.py +141 -36
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/filter_result.py +11 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/functional.py +2 -2
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/schema.py +21 -13
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/operations.rst +1 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/pyproject.toml +1 -1
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/test_collection.py +12 -6
- dataframely-2.13.0/tests/collection/test_pipe.py +55 -0
- dataframely-2.13.0/tests/collection/test_skip_member_validation.py +108 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.claude/CLAUDE.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.copier-answers.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.envrc +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.gitattributes +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/CODEOWNERS +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/copilot-instructions.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/dependabot.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/instructions/tests.instructions.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/release-drafter.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/build.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/chore.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/ci.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/copilot-setup-steps.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/nightly.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/release-drafter.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/scorecard.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.gitignore +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.lefthook.yaml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.prettierignore +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.prettierrc +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/.readthedocs.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/AGENTS.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/Cargo.lock +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/Cargo.toml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/LICENSE +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/README.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/SECURITY.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_base_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_compat.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_deprecation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_filter.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_match_to_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_native.pyi +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_plugin.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_pydantic.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_rule.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/_exc.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/_fsspec.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/constants.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/delta.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/parquet.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_typing.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/filter_result.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_mixins.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_registry.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_utils.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/any.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/array.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/binary.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/bool.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/categorical.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/datetime.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/decimal.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/enum.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/float.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/integer.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/list.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/object.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/string.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/struct.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/config.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/exc.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/experimental/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/experimental/infer_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/py.typed +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/random.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/const.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/factory.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/mask.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/rules.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docker-compose.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_static/custom.css +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_static/favicon.ico +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/autosummary/class.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/autosummary/method.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/classes/column.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/classes/error.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/classes/filter_result.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/generation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/io.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/metadata.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/validation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/columns/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/errors/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/experimental/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/filter_result/failure_info.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/filter_result/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/misc/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/conversion.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/generation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/io.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/metadata.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/validation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/conf.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/css/custom.css +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/coding-agents.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/development.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/examples/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/examples/real-world.ipynb +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/faq.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/column-metadata.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/data-generation.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/lazy-validation.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/primary-keys.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/serialization.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/sql-generation.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/migration/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/migration/v1-v2.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/quickstart.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/docs/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/pixi.lock +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/pixi.toml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/rust-toolchain.toml +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/skills/SKILL.md +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/lib.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/mod.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/rule_failure.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/utils.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/validation_error.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/regex/errdefs.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/regex/mod.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/src/regex/repr.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/conftest.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/test_failure.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/test_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_cast.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_collection_future_annotations.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_create_empty.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_dataframe_members.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_filter_one_to_n.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_filter_validate.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_ignore_in_filter.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_implementation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_join.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_matches.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_optional_members.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_propagate_row_failures.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_repr.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_sample.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_validate_input.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_any.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_array.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_binary.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_datetime.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_decimal.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_enum.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_float.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_integer.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_list.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_object.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_string.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_struct.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_alias.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_check.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_default_dtypes.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_description.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_matches.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_metadata.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_polars_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_pyarrow.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_pydantic.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_rules.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_sample.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_sqlalchemy_columns.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_str.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_utils.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/conftest.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/core_validation/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/core_validation/test_match_to_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/core_validation/test_rule_evaluation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/experimental/test_infer_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/failure_info/test_storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/functional/test_concat.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/functional/test_relationships.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_cast.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_create_empty.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_create_empty_if_none.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_filter.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_inheritance.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_matches.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_pydantic_model.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_read_write_parquet.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_repr.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_rule_implementation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_sample.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_validate.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/storage/test_delta.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/storage/test_fsspec.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_compat.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_config.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_deprecation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_factory.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_native_regex.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_pydantic.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_random.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_typing.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
3
|
|
|
4
4
|
import datetime as dt
|
|
5
|
-
from typing import TypeVar
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
6
|
|
|
7
7
|
import polars as pl
|
|
8
8
|
from polars.datatypes import DataTypeClass
|
|
@@ -41,9 +41,18 @@ def timedelta_matches_resolution(d: dt.timedelta, resolution: str) -> bool:
|
|
|
41
41
|
return datetime_matches_resolution(EPOCH_DATETIME + d, resolution)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def collect_if(lf: pl.LazyFrame, condition: bool) -> pl.
|
|
45
|
-
"""Collect a lazy frame
|
|
46
|
-
frame."""
|
|
44
|
+
def collect_if(lf: pl.LazyFrame, condition: bool, **kwargs: Any) -> pl.LazyFrame:
|
|
45
|
+
"""Collect a lazy frame based on `condition`."""
|
|
47
46
|
if condition:
|
|
48
|
-
return lf.collect()
|
|
47
|
+
return lf.collect(**kwargs).lazy()
|
|
49
48
|
return lf
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def collect_all_if(
|
|
52
|
+
lfs: dict[str, pl.LazyFrame], condition: bool, **kwargs: Any
|
|
53
|
+
) -> dict[str, pl.LazyFrame]:
|
|
54
|
+
"""Collect the lazy frames in the dictionary based on `condition`."""
|
|
55
|
+
if condition:
|
|
56
|
+
dfs = pl.collect_all(lfs.values(), **kwargs)
|
|
57
|
+
return {k: v.lazy() for k, v in zip(lfs.keys(), dfs)}
|
|
58
|
+
return lfs
|
|
@@ -8,11 +8,21 @@ import sys
|
|
|
8
8
|
import textwrap
|
|
9
9
|
import warnings
|
|
10
10
|
from abc import ABC
|
|
11
|
-
from collections.abc import Iterable, Mapping, Sequence
|
|
11
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
12
12
|
from dataclasses import asdict
|
|
13
13
|
from json import JSONDecodeError
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import
|
|
15
|
+
from typing import (
|
|
16
|
+
IO,
|
|
17
|
+
Annotated,
|
|
18
|
+
Any,
|
|
19
|
+
Concatenate,
|
|
20
|
+
Literal,
|
|
21
|
+
ParamSpec,
|
|
22
|
+
TypeVar,
|
|
23
|
+
cast,
|
|
24
|
+
overload,
|
|
25
|
+
)
|
|
16
26
|
|
|
17
27
|
import polars as pl
|
|
18
28
|
import polars.exceptions as plexc
|
|
@@ -21,7 +31,7 @@ from dataframely._compat import deltalake
|
|
|
21
31
|
from dataframely._filter import Filter
|
|
22
32
|
from dataframely._native import format_rule_failures
|
|
23
33
|
from dataframely._plugin import all_rules_required
|
|
24
|
-
from dataframely._polars import FrameType,
|
|
34
|
+
from dataframely._polars import FrameType, collect_all_if
|
|
25
35
|
from dataframely._serialization import (
|
|
26
36
|
SERIALIZATION_FORMAT_VERSION,
|
|
27
37
|
SchemaJSONDecoder,
|
|
@@ -53,6 +63,9 @@ else:
|
|
|
53
63
|
|
|
54
64
|
_FILTER_COLUMN_PREFIX = "__DATAFRAMELY_FILTER_COLUMN__"
|
|
55
65
|
|
|
66
|
+
P = ParamSpec("P")
|
|
67
|
+
T = TypeVar("T")
|
|
68
|
+
|
|
56
69
|
|
|
57
70
|
class Collection(BaseCollection, ABC):
|
|
58
71
|
"""Base class for all collections of data frames with a predefined schema.
|
|
@@ -367,7 +380,14 @@ class Collection(BaseCollection, ABC):
|
|
|
367
380
|
|
|
368
381
|
@classmethod
|
|
369
382
|
def validate(
|
|
370
|
-
cls,
|
|
383
|
+
cls,
|
|
384
|
+
data: Mapping[str, FrameType],
|
|
385
|
+
/,
|
|
386
|
+
*,
|
|
387
|
+
cast: bool = False,
|
|
388
|
+
eager: bool = True,
|
|
389
|
+
skip_member_validation: bool = False,
|
|
390
|
+
**kwargs: Any,
|
|
371
391
|
) -> Self:
|
|
372
392
|
"""Validate that a set of data frames satisfy the collection's invariants.
|
|
373
393
|
|
|
@@ -387,6 +407,13 @@ class Collection(BaseCollection, ABC):
|
|
|
387
407
|
:meth:`~polars.LazyFrame.collect` on the individual member or
|
|
388
408
|
:meth:`collect_all` on the collection. Note that, in the latter case,
|
|
389
409
|
information from error messages is limited.
|
|
410
|
+
skip_member_validation: Whether to skip validating individual members and only
|
|
411
|
+
apply the collection filters. **Use this option with caution** as it
|
|
412
|
+
requires the caller to ensure that the individual members have been
|
|
413
|
+
validated. This option is particularly useful in performance-critical
|
|
414
|
+
scenarios where the members are known to be valid.
|
|
415
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
|
|
416
|
+
:meth:`polars.LazyFrame.collect` when `eager=True`.
|
|
390
417
|
|
|
391
418
|
Raises:
|
|
392
419
|
ValueError: If an insufficient set of input data frames is provided, i.e. if
|
|
@@ -408,7 +435,13 @@ class Collection(BaseCollection, ABC):
|
|
|
408
435
|
if eager:
|
|
409
436
|
# If we perform the validation eagerly, we call filter and check the failure
|
|
410
437
|
# information to properly construct a useful error message.
|
|
411
|
-
filtered, failures = cls.filter(
|
|
438
|
+
filtered, failures = cls.filter(
|
|
439
|
+
data,
|
|
440
|
+
cast=cast,
|
|
441
|
+
eager=True,
|
|
442
|
+
skip_member_validation=skip_member_validation,
|
|
443
|
+
**kwargs,
|
|
444
|
+
)
|
|
412
445
|
if any(len(failure) > 0 for failure in failures.values()):
|
|
413
446
|
errors: dict[str, str] = {}
|
|
414
447
|
for member, failure in failures.items():
|
|
@@ -439,7 +472,17 @@ class Collection(BaseCollection, ABC):
|
|
|
439
472
|
# efficiently as we cannot easily propagate error messages from different
|
|
440
473
|
# members anyways.
|
|
441
474
|
members: dict[str, pl.LazyFrame] = {
|
|
442
|
-
name:
|
|
475
|
+
name: (
|
|
476
|
+
(
|
|
477
|
+
member.schema.cast(data[name].lazy())
|
|
478
|
+
if cast
|
|
479
|
+
else data[name].lazy()
|
|
480
|
+
)
|
|
481
|
+
if skip_member_validation
|
|
482
|
+
else member.schema.validate(
|
|
483
|
+
data[name].lazy(), cast=cast, eager=False
|
|
484
|
+
)
|
|
485
|
+
)
|
|
443
486
|
for name, member in cls.members().items()
|
|
444
487
|
if name in data
|
|
445
488
|
}
|
|
@@ -476,7 +519,9 @@ class Collection(BaseCollection, ABC):
|
|
|
476
519
|
return cls._init(members)
|
|
477
520
|
|
|
478
521
|
@classmethod
|
|
479
|
-
def is_valid(
|
|
522
|
+
def is_valid(
|
|
523
|
+
cls, data: Mapping[str, FrameType], /, *, cast: bool = False, **kwargs: Any
|
|
524
|
+
) -> bool:
|
|
480
525
|
"""Utility method to check whether :meth:`validate` raises an exception.
|
|
481
526
|
|
|
482
527
|
Args:
|
|
@@ -485,6 +530,8 @@ class Collection(BaseCollection, ABC):
|
|
|
485
530
|
the member as key.
|
|
486
531
|
cast: Whether columns with a wrong data type in the member data frame are
|
|
487
532
|
cast to their schemas' defined data types if possible.
|
|
533
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
|
|
534
|
+
:meth:`polars.LazyFrame.collect`.
|
|
488
535
|
|
|
489
536
|
Returns:
|
|
490
537
|
Whether the provided members satisfy the invariants of the collection.
|
|
@@ -499,7 +546,7 @@ class Collection(BaseCollection, ABC):
|
|
|
499
546
|
members: dict[str, pl.LazyFrame] = {}
|
|
500
547
|
for member, schema in cls.member_schemas().items():
|
|
501
548
|
if member in data:
|
|
502
|
-
if not schema.is_valid(data[member], cast=cast):
|
|
549
|
+
if not schema.is_valid(data[member], cast=cast, **kwargs):
|
|
503
550
|
return False
|
|
504
551
|
members[member] = data[member].lazy()
|
|
505
552
|
|
|
@@ -510,9 +557,12 @@ class Collection(BaseCollection, ABC):
|
|
|
510
557
|
keep = [filter.logic(result_cls).select(primary_key) for filter in filters]
|
|
511
558
|
joined = _join_all(*keep, on=primary_key, how="inner")
|
|
512
559
|
removed_rows = pl.collect_all(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
560
|
+
(
|
|
561
|
+
data[member].lazy().join(joined, on=primary_key, how="anti")
|
|
562
|
+
for member in cls.members()
|
|
563
|
+
if member in data
|
|
564
|
+
),
|
|
565
|
+
**kwargs,
|
|
516
566
|
)
|
|
517
567
|
return all(df.is_empty() for df in removed_rows)
|
|
518
568
|
|
|
@@ -522,7 +572,14 @@ class Collection(BaseCollection, ABC):
|
|
|
522
572
|
|
|
523
573
|
@classmethod
|
|
524
574
|
def filter(
|
|
525
|
-
cls,
|
|
575
|
+
cls,
|
|
576
|
+
data: Mapping[str, FrameType],
|
|
577
|
+
/,
|
|
578
|
+
*,
|
|
579
|
+
cast: bool = False,
|
|
580
|
+
eager: bool = True,
|
|
581
|
+
skip_member_validation: bool = False,
|
|
582
|
+
**kwargs: Any,
|
|
526
583
|
) -> CollectionFilterResult[Self]:
|
|
527
584
|
"""Filter the members data frame by their schemas and the collection's filters.
|
|
528
585
|
|
|
@@ -538,6 +595,13 @@ class Collection(BaseCollection, ABC):
|
|
|
538
595
|
eager: Whether the filter operation should be performed eagerly.
|
|
539
596
|
Note that until https://github.com/pola-rs/polars/pull/24129 is
|
|
540
597
|
released, eagerly filtering can provide significant speedups.
|
|
598
|
+
skip_member_validation: Whether to skip filtering individual members and only
|
|
599
|
+
apply the collection filters. **Use this option with caution** as it
|
|
600
|
+
requires the caller to ensure that the individual members have been
|
|
601
|
+
validated. This option is particularly useful in performance-critical
|
|
602
|
+
scenarios where the members are known to already be valid.
|
|
603
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
|
|
604
|
+
:meth:`polars.LazyFrame.collect` when `eager=True`.
|
|
541
605
|
|
|
542
606
|
Returns:
|
|
543
607
|
A named tuple with fields `result` and `failure`. The `result` field
|
|
@@ -579,10 +643,20 @@ class Collection(BaseCollection, ABC):
|
|
|
579
643
|
if member.is_optional and member_name not in data:
|
|
580
644
|
continue
|
|
581
645
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
646
|
+
if skip_member_validation:
|
|
647
|
+
results[member_name] = (
|
|
648
|
+
member.schema.cast(data[member_name].lazy())
|
|
649
|
+
if cast
|
|
650
|
+
else data[member_name].lazy()
|
|
651
|
+
)
|
|
652
|
+
failures[member_name] = FailureInfo._create_empty(
|
|
653
|
+
member.schema, with_casting_rules=cast
|
|
654
|
+
)
|
|
655
|
+
else:
|
|
656
|
+
member_result, failures[member_name] = member.schema.filter(
|
|
657
|
+
data[member_name].lazy(), cast=cast, eager=eager, **kwargs
|
|
658
|
+
)
|
|
659
|
+
results[member_name] = member_result.lazy()
|
|
586
660
|
|
|
587
661
|
# Once we've done that, we can apply the filters on this collection. To this end,
|
|
588
662
|
# we iterate over all filters and store the filter results.
|
|
@@ -592,28 +666,25 @@ class Collection(BaseCollection, ABC):
|
|
|
592
666
|
result_cls = cls._init(results)
|
|
593
667
|
primary_key = cls.common_primary_key()
|
|
594
668
|
|
|
595
|
-
keep
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
.pipe(collect_if, eager)
|
|
601
|
-
.lazy()
|
|
602
|
-
)
|
|
669
|
+
keep = {
|
|
670
|
+
name: filter.logic(result_cls).select(primary_key)
|
|
671
|
+
for name, filter in filters.items()
|
|
672
|
+
}
|
|
673
|
+
keep = collect_all_if(keep, eager, **kwargs)
|
|
603
674
|
|
|
604
|
-
drop: dict[str, pl.LazyFrame] = {
|
|
605
|
-
|
|
606
|
-
annotation_column = f"{failure_propagating_member}|failure_propagation"
|
|
607
|
-
drop[annotation_column] = (
|
|
675
|
+
drop: dict[str, pl.LazyFrame] = {
|
|
676
|
+
f"{failure_propagating_member}|failure_propagation": (
|
|
608
677
|
failures[failure_propagating_member]
|
|
609
678
|
._lf.select(primary_key)
|
|
610
679
|
.unique()
|
|
611
|
-
.pipe(collect_if, eager)
|
|
612
|
-
.lazy()
|
|
613
680
|
)
|
|
681
|
+
for failure_propagating_member in failure_propagating_members
|
|
682
|
+
}
|
|
683
|
+
drop = collect_all_if(drop, eager, **kwargs)
|
|
614
684
|
|
|
615
685
|
# Now we can iterate over the results and left-join onto each individual
|
|
616
|
-
# filter to obtain independent boolean indicators of whether to keep the row
|
|
686
|
+
# filter to obtain independent boolean indicators of whether to keep the row.
|
|
687
|
+
lfs_with_eval: dict[str, pl.LazyFrame] = {}
|
|
617
688
|
for member_name, filtered in results.items():
|
|
618
689
|
member_info = cls.members()[member_name]
|
|
619
690
|
if member_info.ignored_in_filters:
|
|
@@ -635,7 +706,11 @@ class Collection(BaseCollection, ABC):
|
|
|
635
706
|
maintain_order="left",
|
|
636
707
|
).with_columns(pl.col(name).fill_null(True))
|
|
637
708
|
|
|
638
|
-
|
|
709
|
+
lfs_with_eval[member_name] = lf_with_eval
|
|
710
|
+
|
|
711
|
+
lfs_with_eval = collect_all_if(lfs_with_eval, eager, **kwargs)
|
|
712
|
+
for member_name, lf_with_eval in lfs_with_eval.items():
|
|
713
|
+
member_info = cls.members()[member_name]
|
|
639
714
|
|
|
640
715
|
# Filtering `lf_with_eval` by the rows for which all joins
|
|
641
716
|
# "succeeded", we can identify the rows that pass all the filters. We
|
|
@@ -700,7 +775,7 @@ class Collection(BaseCollection, ABC):
|
|
|
700
775
|
|
|
701
776
|
result = CollectionFilterResult(cls._init(results), failures)
|
|
702
777
|
if eager:
|
|
703
|
-
return result.collect_all()
|
|
778
|
+
return result.collect_all(**kwargs)
|
|
704
779
|
return result
|
|
705
780
|
|
|
706
781
|
def join(
|
|
@@ -795,22 +870,52 @@ class Collection(BaseCollection, ABC):
|
|
|
795
870
|
|
|
796
871
|
# ---------------------------------- COLLECTION ---------------------------------- #
|
|
797
872
|
|
|
798
|
-
def collect_all(self) -> Self:
|
|
873
|
+
def collect_all(self, **kwargs: Any) -> Self:
|
|
799
874
|
"""Collect all members of the collection.
|
|
800
875
|
|
|
801
876
|
This method collects all members in parallel for maximum efficiency. It is
|
|
802
877
|
particularly useful when :meth:`filter` is called with lazy frame inputs.
|
|
803
878
|
|
|
879
|
+
Args:
|
|
880
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all`.
|
|
881
|
+
|
|
804
882
|
Returns:
|
|
805
883
|
The same collection with all members collected once. Members annotated
|
|
806
884
|
with :class:`~dataframely.DataFrame` are returned as DataFrames, while
|
|
807
885
|
members annotated with :class:`~dataframely.LazyFrame` are returned as
|
|
808
|
-
"shallow-lazy" frames (obtained by calling
|
|
886
|
+
"shallow-lazy" frames (obtained by calling `.collect().lazy()`).
|
|
809
887
|
"""
|
|
810
888
|
lazy_dict = self.to_dict()
|
|
811
|
-
dfs = pl.collect_all(lazy_dict.values())
|
|
889
|
+
dfs = pl.collect_all(lazy_dict.values(), **kwargs)
|
|
812
890
|
return self._init(dict(zip(lazy_dict, dfs)))
|
|
813
891
|
|
|
892
|
+
def pipe(
|
|
893
|
+
self,
|
|
894
|
+
function: Callable[Concatenate[Self, P], T],
|
|
895
|
+
*args: P.args,
|
|
896
|
+
**kwargs: P.kwargs,
|
|
897
|
+
) -> T:
|
|
898
|
+
"""Apply a function to this collection.
|
|
899
|
+
|
|
900
|
+
This method allows chaining operations on a collection in a fluent style,
|
|
901
|
+
analogously to :meth:`polars.LazyFrame.pipe`.
|
|
902
|
+
|
|
903
|
+
Args:
|
|
904
|
+
function: The callable to apply. It receives this collection as its first
|
|
905
|
+
argument, followed by any additional ``args`` and ``kwargs``.
|
|
906
|
+
args: Additional positional arguments to pass to ``function``.
|
|
907
|
+
kwargs: Additional keyword arguments to pass to ``function``.
|
|
908
|
+
|
|
909
|
+
Returns:
|
|
910
|
+
The return value of ``function`` when called as described.
|
|
911
|
+
|
|
912
|
+
Example:
|
|
913
|
+
>>> def add_prefix(collection: MyCollection, prefix: str) -> MyCollection:
|
|
914
|
+
... ...
|
|
915
|
+
>>> result = my_collection.pipe(add_prefix, prefix="foo")
|
|
916
|
+
"""
|
|
917
|
+
return function(self, *args, **kwargs)
|
|
918
|
+
|
|
814
919
|
# --------------------------------- SERIALIZATION -------------------------------- #
|
|
815
920
|
|
|
816
921
|
@classmethod
|
|
@@ -106,6 +106,17 @@ class FailureInfo(Generic[S]):
|
|
|
106
106
|
self._rule_columns = rule_columns
|
|
107
107
|
self.schema = schema
|
|
108
108
|
|
|
109
|
+
@classmethod
|
|
110
|
+
def _create_empty(cls, schema: type[S], with_casting_rules: bool) -> FailureInfo[S]:
|
|
111
|
+
rules = schema._validation_rules(with_cast=with_casting_rules)
|
|
112
|
+
lf = pl.LazyFrame(
|
|
113
|
+
schema={
|
|
114
|
+
**schema.to_polars_schema(), # type: ignore
|
|
115
|
+
**{rule: pl.Boolean for rule in rules},
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
return cls(lf=lf, rule_columns=list(rules.keys()), schema=schema)
|
|
119
|
+
|
|
109
120
|
@cached_property
|
|
110
121
|
def _df(self) -> pl.DataFrame:
|
|
111
122
|
return self._lf.collect()
|
|
@@ -84,9 +84,9 @@ def require_relationship_one_to_at_least_one(
|
|
|
84
84
|
columns, filtered to ensure a 1:{1,N} relationship.
|
|
85
85
|
"""
|
|
86
86
|
if drop_duplicates:
|
|
87
|
-
return lhs.unique(on, keep="none").join(rhs
|
|
87
|
+
return lhs.unique(on, keep="none").join(rhs, on=on, how="semi")
|
|
88
88
|
|
|
89
|
-
return lhs.join(rhs
|
|
89
|
+
return lhs.join(rhs, on=on, how="semi")
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
# ------------------------------------------------------------------------------------ #
|
|
@@ -501,6 +501,7 @@ class Schema(BaseSchema, ABC):
|
|
|
501
501
|
*,
|
|
502
502
|
cast: bool = False,
|
|
503
503
|
eager: Literal[True] = True,
|
|
504
|
+
**kwargs: Any,
|
|
504
505
|
) -> DataFrame[Self]: ...
|
|
505
506
|
|
|
506
507
|
@overload
|
|
@@ -512,6 +513,7 @@ class Schema(BaseSchema, ABC):
|
|
|
512
513
|
*,
|
|
513
514
|
cast: bool = False,
|
|
514
515
|
eager: Literal[False],
|
|
516
|
+
**kwargs: Any,
|
|
515
517
|
) -> LazyFrame[Self]: ...
|
|
516
518
|
|
|
517
519
|
@overload
|
|
@@ -523,6 +525,7 @@ class Schema(BaseSchema, ABC):
|
|
|
523
525
|
*,
|
|
524
526
|
cast: bool = False,
|
|
525
527
|
eager: bool,
|
|
528
|
+
**kwargs: Any,
|
|
526
529
|
) -> DataFrame[Self] | LazyFrame[Self]: ...
|
|
527
530
|
|
|
528
531
|
@classmethod
|
|
@@ -533,6 +536,7 @@ class Schema(BaseSchema, ABC):
|
|
|
533
536
|
*,
|
|
534
537
|
cast: bool = False,
|
|
535
538
|
eager: bool = True,
|
|
539
|
+
**kwargs: Any,
|
|
536
540
|
) -> DataFrame[Self] | LazyFrame[Self]:
|
|
537
541
|
"""Validate that a data frame satisfies the schema.
|
|
538
542
|
|
|
@@ -554,6 +558,8 @@ class Schema(BaseSchema, ABC):
|
|
|
554
558
|
not surface *all* validation issues as the validation is aborted
|
|
555
559
|
once the first failure is encountered. Likewise, the reported
|
|
556
560
|
validation failure can be non-deterministic.
|
|
561
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.LazyFrame.collect`
|
|
562
|
+
when `eager=True`.
|
|
557
563
|
|
|
558
564
|
Returns:
|
|
559
565
|
The input eager or lazy frame, wrapped in a generic version of the
|
|
@@ -574,7 +580,7 @@ class Schema(BaseSchema, ABC):
|
|
|
574
580
|
`eager=False`.
|
|
575
581
|
"""
|
|
576
582
|
if eager:
|
|
577
|
-
out, failure = cls.filter(df, cast=cast, eager=True)
|
|
583
|
+
out, failure = cls.filter(df, cast=cast, eager=True, **kwargs)
|
|
578
584
|
if len(failure) > 0:
|
|
579
585
|
counts = failure.counts()
|
|
580
586
|
raise ValidationError(
|
|
@@ -608,7 +614,7 @@ class Schema(BaseSchema, ABC):
|
|
|
608
614
|
|
|
609
615
|
@classmethod
|
|
610
616
|
def is_valid(
|
|
611
|
-
cls, df: pl.DataFrame | pl.LazyFrame, /, *, cast: bool = False
|
|
617
|
+
cls, df: pl.DataFrame | pl.LazyFrame, /, *, cast: bool = False, **kwargs: Any
|
|
612
618
|
) -> bool:
|
|
613
619
|
"""Check whether a data frame satisfies the schema.
|
|
614
620
|
|
|
@@ -627,6 +633,7 @@ class Schema(BaseSchema, ABC):
|
|
|
627
633
|
cast to the schema's defined data type before running validation. If set
|
|
628
634
|
to `False`, a wrong data type will result in a return value of
|
|
629
635
|
`False`.
|
|
636
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.LazyFrame.collect`.
|
|
630
637
|
|
|
631
638
|
Returns:
|
|
632
639
|
Whether the provided dataframe can be validated with this schema.
|
|
@@ -646,12 +653,12 @@ class Schema(BaseSchema, ABC):
|
|
|
646
653
|
return (
|
|
647
654
|
lf.pipe(with_evaluation_rules, rules)
|
|
648
655
|
.select(all_rules(rules.keys()))
|
|
649
|
-
.collect()
|
|
656
|
+
.collect(**kwargs)
|
|
650
657
|
.item()
|
|
651
658
|
)
|
|
652
659
|
# NOTE: We cannot simply return `True` here as, otherwise, we wouldn't
|
|
653
660
|
# validate the schema.
|
|
654
|
-
return lf.select(pl.lit(True)).collect().item()
|
|
661
|
+
return lf.select(pl.lit(True)).collect(**kwargs).item()
|
|
655
662
|
except SchemaError:
|
|
656
663
|
# If we encounter a schema error, we gracefully handle this as 'invalid'
|
|
657
664
|
return False
|
|
@@ -699,6 +706,7 @@ class Schema(BaseSchema, ABC):
|
|
|
699
706
|
*,
|
|
700
707
|
cast: bool = False,
|
|
701
708
|
eager: bool = True,
|
|
709
|
+
**kwargs: Any,
|
|
702
710
|
) -> FilterResult[Self] | LazyFilterResult[Self]:
|
|
703
711
|
"""Filter the data frame by the rules of this schema, returning `(valid,
|
|
704
712
|
failures)`.
|
|
@@ -709,16 +717,16 @@ class Schema(BaseSchema, ABC):
|
|
|
709
717
|
succeeds.
|
|
710
718
|
|
|
711
719
|
Args:
|
|
712
|
-
df: The data frame to filter for valid rows.
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
cast:
|
|
716
|
-
Whether columns with a wrong data type in the input data frame are
|
|
720
|
+
df: The data frame to filter for valid rows. The data frame is collected
|
|
721
|
+
within this method, regardless of whether a :class:`~polars.DataFrame`
|
|
722
|
+
or :class:`~polars.LazyFrame` is passed.
|
|
723
|
+
cast: Whether columns with a wrong data type in the input data frame are
|
|
717
724
|
cast to the schema's defined data type if possible. Rows for which the
|
|
718
725
|
cast fails for any column are filtered out.
|
|
719
726
|
eager: Whether the filter operation should be performed eagerly. If `False`, the
|
|
720
|
-
returned lazy frame will
|
|
721
|
-
|
|
727
|
+
returned lazy frame will fail to collect if the validation does not pass.
|
|
728
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.LazyFrame.collect`
|
|
729
|
+
when `eager=True`.
|
|
722
730
|
|
|
723
731
|
Returns:
|
|
724
732
|
A tuple of the validated rows in the input data frame (potentially
|
|
@@ -754,8 +762,8 @@ class Schema(BaseSchema, ABC):
|
|
|
754
762
|
match_to_schema, cls, casting=("lenient" if cast else "none")
|
|
755
763
|
)
|
|
756
764
|
if rules := cls._validation_rules(with_cast=cast):
|
|
757
|
-
evaluated = (
|
|
758
|
-
|
|
765
|
+
evaluated = lf.pipe(cls._with_evaluated_rules, rules).pipe(
|
|
766
|
+
collect_if, eager, **kwargs
|
|
759
767
|
)
|
|
760
768
|
filtered = evaluated.filter(pl.col(_COLUMN_VALID)).select(
|
|
761
769
|
cls.column_names()
|
|
@@ -27,7 +27,7 @@ description = "A declarative, polars-native data frame validation library"
|
|
|
27
27
|
name = "dataframely"
|
|
28
28
|
readme = "README.md"
|
|
29
29
|
requires-python = ">=3.10"
|
|
30
|
-
version = "2.
|
|
30
|
+
version = "2.13.0"
|
|
31
31
|
|
|
32
32
|
[project.optional-dependencies]
|
|
33
33
|
deltalake = ["deltalake"]
|
|
@@ -15,13 +15,13 @@ def partitioned_dataset(dataset: pl.DataFrame) -> dict[str, pl.DataFrame]:
|
|
|
15
15
|
"elevation",
|
|
16
16
|
"aspect",
|
|
17
17
|
"slope",
|
|
18
|
-
idx=pl.int_range(pl.len(), dtype=pl.UInt32),
|
|
18
|
+
idx=pl.int_range(pl.len(), dtype=pl.UInt32).shuffle(),
|
|
19
19
|
),
|
|
20
20
|
"second": dataset.select(
|
|
21
21
|
"horizontal_distance_to_hydrology",
|
|
22
22
|
"vertical_distance_to_hydrology",
|
|
23
23
|
"horizontal_distance_to_roadways",
|
|
24
|
-
idx=pl.int_range(pl.len(), dtype=pl.UInt32),
|
|
24
|
+
idx=pl.int_range(pl.len(), dtype=pl.UInt32).shuffle(),
|
|
25
25
|
),
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -95,18 +95,24 @@ class MultiFilterCollection(dy.Collection):
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@pytest.mark.benchmark(group="collection-filter-multi")
|
|
98
|
+
@pytest.mark.parametrize("engine", ["in-memory", "streaming"])
|
|
98
99
|
def test_multi_filter_validate(
|
|
99
|
-
benchmark: BenchmarkFixture,
|
|
100
|
+
benchmark: BenchmarkFixture,
|
|
101
|
+
partitioned_dataset: dict[str, pl.DataFrame],
|
|
102
|
+
engine: str,
|
|
100
103
|
) -> None:
|
|
101
|
-
benchmark(MultiFilterCollection.validate, partitioned_dataset)
|
|
104
|
+
benchmark(MultiFilterCollection.validate, partitioned_dataset, engine=engine)
|
|
102
105
|
|
|
103
106
|
|
|
104
107
|
@pytest.mark.benchmark(group="collection-filter-multi")
|
|
108
|
+
@pytest.mark.parametrize("engine", ["in-memory", "streaming"])
|
|
105
109
|
def test_multi_filter_filter(
|
|
106
|
-
benchmark: BenchmarkFixture,
|
|
110
|
+
benchmark: BenchmarkFixture,
|
|
111
|
+
partitioned_dataset: dict[str, pl.DataFrame],
|
|
112
|
+
engine: str,
|
|
107
113
|
) -> None:
|
|
108
114
|
def benchmark_fn() -> None:
|
|
109
|
-
_, failure = MultiFilterCollection.filter(partitioned_dataset)
|
|
115
|
+
_, failure = MultiFilterCollection.filter(partitioned_dataset, engine=engine)
|
|
110
116
|
_ = [len(f) for f in failure.values()]
|
|
111
117
|
|
|
112
118
|
benchmark(benchmark_fn)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright (c) QuantCo 2025-2026
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
|
|
4
|
+
import dataframely as dy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SchemaOne(dy.Schema):
|
|
8
|
+
id = dy.Int64(primary_key=True)
|
|
9
|
+
name = dy.String(nullable=False)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SchemaTwo(dy.Schema):
|
|
13
|
+
id = dy.Int64(primary_key=True)
|
|
14
|
+
name = dy.String(nullable=False)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MyCollection(dy.Collection):
|
|
18
|
+
member_one: dy.LazyFrame[SchemaOne]
|
|
19
|
+
member_two: dy.LazyFrame[SchemaTwo]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_pipe_passes_self() -> None:
|
|
23
|
+
# Arrange
|
|
24
|
+
collection = MyCollection.sample(overrides=[{"id": 1}, {"id": 2}])
|
|
25
|
+
|
|
26
|
+
# Act
|
|
27
|
+
result = collection.pipe(lambda c: c)
|
|
28
|
+
|
|
29
|
+
# Assert
|
|
30
|
+
assert result is collection
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_pipe_forwards_args_and_kwargs() -> None:
|
|
34
|
+
# Arrange
|
|
35
|
+
collection = MyCollection.sample(overrides=[{"id": 1}, {"id": 2}])
|
|
36
|
+
|
|
37
|
+
def combine(c: MyCollection, prefix: str, *, suffix: str) -> str:
|
|
38
|
+
return f"{prefix}{type(c).__name__}{suffix}"
|
|
39
|
+
|
|
40
|
+
# Act
|
|
41
|
+
result = collection.pipe(combine, "pre-", suffix="-post")
|
|
42
|
+
|
|
43
|
+
# Assert
|
|
44
|
+
assert result == "pre-MyCollection-post"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_pipe_returns_arbitrary_type() -> None:
|
|
48
|
+
# Arrange
|
|
49
|
+
collection = MyCollection.sample(overrides=[{"id": 1}, {"id": 2}, {"id": 3}])
|
|
50
|
+
|
|
51
|
+
# Act
|
|
52
|
+
result = collection.pipe(lambda c: c.member_one.collect().height)
|
|
53
|
+
|
|
54
|
+
# Assert
|
|
55
|
+
assert result == 3
|