dataframely 2.11.0__tar.gz → 2.12.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.12.0}/PKG-INFO +1 -1
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_polars.py +14 -5
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/collection/collection.py +99 -32
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/functional.py +2 -2
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/schema.py +21 -13
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/collection/operations.rst +1 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/pyproject.toml +1 -1
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/benches/test_collection.py +12 -6
- dataframely-2.12.0/tests/collection/test_pipe.py +55 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.claude/CLAUDE.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.copier-answers.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.envrc +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.gitattributes +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/CODEOWNERS +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/copilot-instructions.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/dependabot.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/instructions/tests.instructions.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/release-drafter.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/build.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/chore.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/ci.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/copilot-setup-steps.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/nightly.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/release-drafter.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.github/workflows/scorecard.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.gitignore +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.lefthook.yaml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.prettierignore +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.prettierrc +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/.readthedocs.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/AGENTS.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/Cargo.lock +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/Cargo.toml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/LICENSE +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/README.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/SECURITY.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_base_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_compat.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_deprecation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_filter.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_match_to_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_native.pyi +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_plugin.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_pydantic.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_rule.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/_exc.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/_fsspec.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/constants.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/delta.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_storage/parquet.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/_typing.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/collection/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/collection/_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/collection/filter_result.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/_mixins.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/_registry.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/_utils.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/any.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/array.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/binary.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/bool.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/categorical.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/datetime.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/decimal.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/enum.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/float.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/integer.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/list.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/object.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/string.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/columns/struct.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/config.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/exc.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/experimental/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/experimental/infer_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/filter_result.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/py.typed +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/random.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/testing/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/testing/const.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/testing/factory.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/testing/mask.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/testing/rules.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/dataframely/testing/storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docker-compose.yml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_static/custom.css +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_static/favicon.ico +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_templates/autosummary/class.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_templates/autosummary/method.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_templates/classes/column.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_templates/classes/error.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/_templates/classes/filter_result.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/collection/generation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/collection/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/collection/io.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/collection/metadata.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/collection/validation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/columns/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/errors/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/experimental/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/filter_result/failure_info.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/filter_result/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/misc/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/schema/conversion.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/schema/generation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/schema/index.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/schema/io.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/schema/metadata.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/api/schema/validation.rst +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/conf.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/css/custom.css +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/coding-agents.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/development.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/examples/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/examples/real-world.ipynb +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/faq.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/column-metadata.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/data-generation.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/lazy-validation.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/primary-keys.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/serialization.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/features/sql-generation.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/migration/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/migration/v1-v2.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/guides/quickstart.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/docs/index.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/pixi.lock +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/pixi.toml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/rust-toolchain.toml +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/skills/SKILL.md +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/lib.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/polars_plugin/mod.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/polars_plugin/rule_failure.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/polars_plugin/utils.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/polars_plugin/validation_error.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/regex/errdefs.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/regex/mod.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/src/regex/repr.rs +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/benches/conftest.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/benches/test_failure.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/benches/test_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_cast.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_collection_future_annotations.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_create_empty.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_dataframe_members.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_filter_one_to_n.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_filter_validate.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_ignore_in_filter.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_implementation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_join.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_matches.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_optional_members.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_propagate_row_failures.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_repr.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_sample.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/collection/test_validate_input.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_any.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_array.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_binary.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_datetime.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_decimal.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_enum.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_float.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_integer.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_list.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_object.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_string.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/column_types/test_struct.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_alias.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_check.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_default_dtypes.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_description.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_matches.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_metadata.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_polars_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_pyarrow.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_pydantic.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_rules.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_sample.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_sqlalchemy_columns.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_str.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/columns/test_utils.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/conftest.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/core_validation/__init__.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/core_validation/test_match_to_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/core_validation/test_rule_evaluation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/experimental/test_infer_schema.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/failure_info/test_storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/functional/test_concat.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/functional/test_relationships.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_base.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_cast.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_create_empty.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_create_empty_if_none.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_filter.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_inheritance.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_matches.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_pydantic_model.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_read_write_parquet.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_repr.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_rule_implementation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_sample.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_storage.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/schema/test_validate.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/storage/test_delta.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/storage/test_fsspec.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_compat.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_config.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_deprecation.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_factory.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_native_regex.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_pydantic.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_random.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.0}/tests/test_serialization.py +0 -0
- {dataframely-2.11.0 → dataframely-2.12.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,13 @@ 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
|
+
**kwargs: Any,
|
|
371
390
|
) -> Self:
|
|
372
391
|
"""Validate that a set of data frames satisfy the collection's invariants.
|
|
373
392
|
|
|
@@ -387,6 +406,8 @@ class Collection(BaseCollection, ABC):
|
|
|
387
406
|
:meth:`~polars.LazyFrame.collect` on the individual member or
|
|
388
407
|
:meth:`collect_all` on the collection. Note that, in the latter case,
|
|
389
408
|
information from error messages is limited.
|
|
409
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
|
|
410
|
+
:meth:`polars.LazyFrame.collect` when `eager=True`.
|
|
390
411
|
|
|
391
412
|
Raises:
|
|
392
413
|
ValueError: If an insufficient set of input data frames is provided, i.e. if
|
|
@@ -408,7 +429,7 @@ class Collection(BaseCollection, ABC):
|
|
|
408
429
|
if eager:
|
|
409
430
|
# If we perform the validation eagerly, we call filter and check the failure
|
|
410
431
|
# information to properly construct a useful error message.
|
|
411
|
-
filtered, failures = cls.filter(data, cast=cast, eager=True)
|
|
432
|
+
filtered, failures = cls.filter(data, cast=cast, eager=True, **kwargs)
|
|
412
433
|
if any(len(failure) > 0 for failure in failures.values()):
|
|
413
434
|
errors: dict[str, str] = {}
|
|
414
435
|
for member, failure in failures.items():
|
|
@@ -476,7 +497,9 @@ class Collection(BaseCollection, ABC):
|
|
|
476
497
|
return cls._init(members)
|
|
477
498
|
|
|
478
499
|
@classmethod
|
|
479
|
-
def is_valid(
|
|
500
|
+
def is_valid(
|
|
501
|
+
cls, data: Mapping[str, FrameType], /, *, cast: bool = False, **kwargs: Any
|
|
502
|
+
) -> bool:
|
|
480
503
|
"""Utility method to check whether :meth:`validate` raises an exception.
|
|
481
504
|
|
|
482
505
|
Args:
|
|
@@ -485,6 +508,8 @@ class Collection(BaseCollection, ABC):
|
|
|
485
508
|
the member as key.
|
|
486
509
|
cast: Whether columns with a wrong data type in the member data frame are
|
|
487
510
|
cast to their schemas' defined data types if possible.
|
|
511
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
|
|
512
|
+
:meth:`polars.LazyFrame.collect`.
|
|
488
513
|
|
|
489
514
|
Returns:
|
|
490
515
|
Whether the provided members satisfy the invariants of the collection.
|
|
@@ -499,7 +524,7 @@ class Collection(BaseCollection, ABC):
|
|
|
499
524
|
members: dict[str, pl.LazyFrame] = {}
|
|
500
525
|
for member, schema in cls.member_schemas().items():
|
|
501
526
|
if member in data:
|
|
502
|
-
if not schema.is_valid(data[member], cast=cast):
|
|
527
|
+
if not schema.is_valid(data[member], cast=cast, **kwargs):
|
|
503
528
|
return False
|
|
504
529
|
members[member] = data[member].lazy()
|
|
505
530
|
|
|
@@ -510,9 +535,12 @@ class Collection(BaseCollection, ABC):
|
|
|
510
535
|
keep = [filter.logic(result_cls).select(primary_key) for filter in filters]
|
|
511
536
|
joined = _join_all(*keep, on=primary_key, how="inner")
|
|
512
537
|
removed_rows = pl.collect_all(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
538
|
+
(
|
|
539
|
+
data[member].lazy().join(joined, on=primary_key, how="anti")
|
|
540
|
+
for member in cls.members()
|
|
541
|
+
if member in data
|
|
542
|
+
),
|
|
543
|
+
**kwargs,
|
|
516
544
|
)
|
|
517
545
|
return all(df.is_empty() for df in removed_rows)
|
|
518
546
|
|
|
@@ -522,7 +550,13 @@ class Collection(BaseCollection, ABC):
|
|
|
522
550
|
|
|
523
551
|
@classmethod
|
|
524
552
|
def filter(
|
|
525
|
-
cls,
|
|
553
|
+
cls,
|
|
554
|
+
data: Mapping[str, FrameType],
|
|
555
|
+
/,
|
|
556
|
+
*,
|
|
557
|
+
cast: bool = False,
|
|
558
|
+
eager: bool = True,
|
|
559
|
+
**kwargs: Any,
|
|
526
560
|
) -> CollectionFilterResult[Self]:
|
|
527
561
|
"""Filter the members data frame by their schemas and the collection's filters.
|
|
528
562
|
|
|
@@ -538,6 +572,8 @@ class Collection(BaseCollection, ABC):
|
|
|
538
572
|
eager: Whether the filter operation should be performed eagerly.
|
|
539
573
|
Note that until https://github.com/pola-rs/polars/pull/24129 is
|
|
540
574
|
released, eagerly filtering can provide significant speedups.
|
|
575
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
|
|
576
|
+
:meth:`polars.LazyFrame.collect` when `eager=True`.
|
|
541
577
|
|
|
542
578
|
Returns:
|
|
543
579
|
A named tuple with fields `result` and `failure`. The `result` field
|
|
@@ -580,7 +616,7 @@ class Collection(BaseCollection, ABC):
|
|
|
580
616
|
continue
|
|
581
617
|
|
|
582
618
|
member_result, failures[member_name] = member.schema.filter(
|
|
583
|
-
data[member_name].lazy(), cast=cast, eager=eager
|
|
619
|
+
data[member_name].lazy(), cast=cast, eager=eager, **kwargs
|
|
584
620
|
)
|
|
585
621
|
results[member_name] = member_result.lazy()
|
|
586
622
|
|
|
@@ -592,28 +628,25 @@ class Collection(BaseCollection, ABC):
|
|
|
592
628
|
result_cls = cls._init(results)
|
|
593
629
|
primary_key = cls.common_primary_key()
|
|
594
630
|
|
|
595
|
-
keep
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
.pipe(collect_if, eager)
|
|
601
|
-
.lazy()
|
|
602
|
-
)
|
|
631
|
+
keep = {
|
|
632
|
+
name: filter.logic(result_cls).select(primary_key)
|
|
633
|
+
for name, filter in filters.items()
|
|
634
|
+
}
|
|
635
|
+
keep = collect_all_if(keep, eager, **kwargs)
|
|
603
636
|
|
|
604
|
-
drop: dict[str, pl.LazyFrame] = {
|
|
605
|
-
|
|
606
|
-
annotation_column = f"{failure_propagating_member}|failure_propagation"
|
|
607
|
-
drop[annotation_column] = (
|
|
637
|
+
drop: dict[str, pl.LazyFrame] = {
|
|
638
|
+
f"{failure_propagating_member}|failure_propagation": (
|
|
608
639
|
failures[failure_propagating_member]
|
|
609
640
|
._lf.select(primary_key)
|
|
610
641
|
.unique()
|
|
611
|
-
.pipe(collect_if, eager)
|
|
612
|
-
.lazy()
|
|
613
642
|
)
|
|
643
|
+
for failure_propagating_member in failure_propagating_members
|
|
644
|
+
}
|
|
645
|
+
drop = collect_all_if(drop, eager, **kwargs)
|
|
614
646
|
|
|
615
647
|
# 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
|
|
648
|
+
# filter to obtain independent boolean indicators of whether to keep the row.
|
|
649
|
+
lfs_with_eval: dict[str, pl.LazyFrame] = {}
|
|
617
650
|
for member_name, filtered in results.items():
|
|
618
651
|
member_info = cls.members()[member_name]
|
|
619
652
|
if member_info.ignored_in_filters:
|
|
@@ -635,7 +668,11 @@ class Collection(BaseCollection, ABC):
|
|
|
635
668
|
maintain_order="left",
|
|
636
669
|
).with_columns(pl.col(name).fill_null(True))
|
|
637
670
|
|
|
638
|
-
|
|
671
|
+
lfs_with_eval[member_name] = lf_with_eval
|
|
672
|
+
|
|
673
|
+
lfs_with_eval = collect_all_if(lfs_with_eval, eager, **kwargs)
|
|
674
|
+
for member_name, lf_with_eval in lfs_with_eval.items():
|
|
675
|
+
member_info = cls.members()[member_name]
|
|
639
676
|
|
|
640
677
|
# Filtering `lf_with_eval` by the rows for which all joins
|
|
641
678
|
# "succeeded", we can identify the rows that pass all the filters. We
|
|
@@ -700,7 +737,7 @@ class Collection(BaseCollection, ABC):
|
|
|
700
737
|
|
|
701
738
|
result = CollectionFilterResult(cls._init(results), failures)
|
|
702
739
|
if eager:
|
|
703
|
-
return result.collect_all()
|
|
740
|
+
return result.collect_all(**kwargs)
|
|
704
741
|
return result
|
|
705
742
|
|
|
706
743
|
def join(
|
|
@@ -795,22 +832,52 @@ class Collection(BaseCollection, ABC):
|
|
|
795
832
|
|
|
796
833
|
# ---------------------------------- COLLECTION ---------------------------------- #
|
|
797
834
|
|
|
798
|
-
def collect_all(self) -> Self:
|
|
835
|
+
def collect_all(self, **kwargs: Any) -> Self:
|
|
799
836
|
"""Collect all members of the collection.
|
|
800
837
|
|
|
801
838
|
This method collects all members in parallel for maximum efficiency. It is
|
|
802
839
|
particularly useful when :meth:`filter` is called with lazy frame inputs.
|
|
803
840
|
|
|
841
|
+
Args:
|
|
842
|
+
kwargs: Keyword arguments passed directly to :meth:`polars.collect_all`.
|
|
843
|
+
|
|
804
844
|
Returns:
|
|
805
845
|
The same collection with all members collected once. Members annotated
|
|
806
846
|
with :class:`~dataframely.DataFrame` are returned as DataFrames, while
|
|
807
847
|
members annotated with :class:`~dataframely.LazyFrame` are returned as
|
|
808
|
-
"shallow-lazy" frames (obtained by calling
|
|
848
|
+
"shallow-lazy" frames (obtained by calling `.collect().lazy()`).
|
|
809
849
|
"""
|
|
810
850
|
lazy_dict = self.to_dict()
|
|
811
|
-
dfs = pl.collect_all(lazy_dict.values())
|
|
851
|
+
dfs = pl.collect_all(lazy_dict.values(), **kwargs)
|
|
812
852
|
return self._init(dict(zip(lazy_dict, dfs)))
|
|
813
853
|
|
|
854
|
+
def pipe(
|
|
855
|
+
self,
|
|
856
|
+
function: Callable[Concatenate[Self, P], T],
|
|
857
|
+
*args: P.args,
|
|
858
|
+
**kwargs: P.kwargs,
|
|
859
|
+
) -> T:
|
|
860
|
+
"""Apply a function to this collection.
|
|
861
|
+
|
|
862
|
+
This method allows chaining operations on a collection in a fluent style,
|
|
863
|
+
analogously to :meth:`polars.LazyFrame.pipe`.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
function: The callable to apply. It receives this collection as its first
|
|
867
|
+
argument, followed by any additional ``args`` and ``kwargs``.
|
|
868
|
+
args: Additional positional arguments to pass to ``function``.
|
|
869
|
+
kwargs: Additional keyword arguments to pass to ``function``.
|
|
870
|
+
|
|
871
|
+
Returns:
|
|
872
|
+
The return value of ``function`` when called as described.
|
|
873
|
+
|
|
874
|
+
Example:
|
|
875
|
+
>>> def add_prefix(collection: MyCollection, prefix: str) -> MyCollection:
|
|
876
|
+
... ...
|
|
877
|
+
>>> result = my_collection.pipe(add_prefix, prefix="foo")
|
|
878
|
+
"""
|
|
879
|
+
return function(self, *args, **kwargs)
|
|
880
|
+
|
|
814
881
|
# --------------------------------- SERIALIZATION -------------------------------- #
|
|
815
882
|
|
|
816
883
|
@classmethod
|
|
@@ -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.12.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|