dataframely 2.10.1__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.10.1 → dataframely-2.12.0}/.copier-answers.yml +1 -1
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/build.yml +4 -4
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/chore.yml +1 -1
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/ci.yml +5 -5
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/copilot-setup-steps.yml +2 -2
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/nightly.yml +2 -2
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/release-drafter.yml +1 -1
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/scorecard.yml +2 -2
- {dataframely-2.10.1 → dataframely-2.12.0}/PKG-INFO +1 -1
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_polars.py +14 -5
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/collection.py +99 -32
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/enum.py +33 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/experimental/infer_schema.py +9 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/functional.py +2 -2
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/schema.py +21 -13
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/operations.rst +1 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/sql-generation.md +35 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/pixi.lock +7835 -7909
- {dataframely-2.10.1 → dataframely-2.12.0}/pyproject.toml +1 -1
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/test_collection.py +12 -6
- dataframely-2.12.0/tests/collection/test_pipe.py +55 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_enum.py +66 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_sqlalchemy_columns.py +63 -1
- {dataframely-2.10.1 → dataframely-2.12.0}/.claude/CLAUDE.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.envrc +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.gitattributes +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/CODEOWNERS +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/copilot-instructions.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/dependabot.yml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/instructions/tests.instructions.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.github/release-drafter.yml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.gitignore +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.lefthook.yaml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.prettierignore +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.prettierrc +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/.readthedocs.yml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/AGENTS.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/Cargo.lock +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/Cargo.toml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/LICENSE +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/README.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/SECURITY.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_base_schema.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_compat.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_deprecation.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_filter.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_match_to_schema.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_native.pyi +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_plugin.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_pydantic.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_rule.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_serialization.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/_base.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/_exc.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/_fsspec.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/constants.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/delta.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/parquet.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_typing.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/_base.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/filter_result.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_base.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_mixins.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_registry.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_utils.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/any.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/array.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/binary.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/bool.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/categorical.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/datetime.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/decimal.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/float.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/integer.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/list.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/object.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/string.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/struct.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/config.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/exc.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/experimental/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/filter_result.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/py.typed +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/random.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/const.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/factory.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/mask.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/rules.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/storage.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docker-compose.yml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_static/custom.css +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_static/favicon.ico +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/autosummary/class.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/autosummary/method.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/classes/column.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/classes/error.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/classes/filter_result.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/generation.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/io.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/metadata.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/validation.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/columns/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/errors/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/experimental/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/filter_result/failure_info.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/filter_result/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/misc/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/conversion.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/generation.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/index.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/io.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/metadata.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/validation.rst +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/conf.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/css/custom.css +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/coding-agents.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/development.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/examples/index.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/examples/real-world.ipynb +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/faq.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/column-metadata.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/data-generation.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/index.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/lazy-validation.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/primary-keys.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/serialization.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/index.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/migration/index.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/migration/v1-v2.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/quickstart.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/docs/index.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/pixi.toml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/rust-toolchain.toml +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/skills/SKILL.md +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/lib.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/mod.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/rule_failure.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/utils.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/validation_error.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/regex/errdefs.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/regex/mod.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/src/regex/repr.rs +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/conftest.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/test_failure.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/test_schema.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_base.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_cast.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_collection_future_annotations.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_create_empty.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_dataframe_members.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_filter_one_to_n.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_filter_validate.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_ignore_in_filter.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_implementation.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_join.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_matches.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_optional_members.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_propagate_row_failures.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_repr.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_sample.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_serialization.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_storage.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_validate_input.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_any.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_array.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_binary.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_datetime.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_decimal.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_float.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_integer.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_list.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_object.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_string.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_struct.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_alias.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_base.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_check.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_default_dtypes.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_description.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_matches.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_metadata.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_polars_schema.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_pyarrow.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_pydantic.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_rules.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_sample.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_str.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_utils.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/conftest.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/core_validation/__init__.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/core_validation/test_match_to_schema.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/core_validation/test_rule_evaluation.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/experimental/test_infer_schema.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/failure_info/test_storage.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/functional/test_concat.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/functional/test_relationships.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_base.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_cast.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_create_empty.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_create_empty_if_none.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_filter.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_inheritance.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_matches.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_pydantic_model.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_read_write_parquet.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_repr.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_rule_implementation.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_sample.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_serialization.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_storage.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_validate.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/storage/test_delta.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/storage/test_fsspec.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_compat.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_config.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_deprecation.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_factory.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_native_regex.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_pydantic.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_random.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_serialization.py +0 -0
- {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_typing.py +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
#
|
|
9
9
|
# If you want to run this as a one-off command, prefix with:
|
|
10
10
|
# `pixi exec --spec copier --spec ruamel.yaml -- {command}`
|
|
11
|
-
_commit: v0.5.
|
|
11
|
+
_commit: v0.5.3
|
|
12
12
|
_src_path: https://github.com/quantco/copier-template-python-open-source
|
|
13
13
|
add_autobump_workflow: false
|
|
14
14
|
author_email: oliver.borchert@quantco.com
|
|
@@ -13,11 +13,11 @@ jobs:
|
|
|
13
13
|
permissions:
|
|
14
14
|
contents: read
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
17
17
|
with:
|
|
18
18
|
fetch-depth: 0
|
|
19
19
|
- name: Set up pixi
|
|
20
|
-
uses: prefix-dev/setup-pixi@
|
|
20
|
+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
|
|
21
21
|
with:
|
|
22
22
|
environments: build
|
|
23
23
|
- name: Set version
|
|
@@ -48,11 +48,11 @@ jobs:
|
|
|
48
48
|
- target-platform: win-64
|
|
49
49
|
os: windows-latest
|
|
50
50
|
steps:
|
|
51
|
-
- uses: actions/checkout@
|
|
51
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
52
52
|
with:
|
|
53
53
|
fetch-depth: 0
|
|
54
54
|
- name: Set up pixi
|
|
55
|
-
uses: prefix-dev/setup-pixi@
|
|
55
|
+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
|
|
56
56
|
with:
|
|
57
57
|
environments: build
|
|
58
58
|
- name: Set version
|
|
@@ -54,6 +54,6 @@ jobs:
|
|
|
54
54
|
pull-requests: write
|
|
55
55
|
steps:
|
|
56
56
|
- name: Assign labels
|
|
57
|
-
uses: release-drafter/release-drafter/autolabeler@
|
|
57
|
+
uses: release-drafter/release-drafter/autolabeler@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0
|
|
58
58
|
with:
|
|
59
59
|
token: ${{ github.token }}
|
|
@@ -19,12 +19,12 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
steps:
|
|
21
21
|
- name: Checkout branch
|
|
22
|
-
uses: actions/checkout@
|
|
22
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
23
23
|
with:
|
|
24
24
|
# needed for 'pre-commit-mirrors-insert-license'
|
|
25
25
|
fetch-depth: 0
|
|
26
26
|
- name: Set up pixi
|
|
27
|
-
uses: prefix-dev/setup-pixi@
|
|
27
|
+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
|
|
28
28
|
with:
|
|
29
29
|
environments: default lint polars-minimal
|
|
30
30
|
- name: Install Rust
|
|
@@ -63,9 +63,9 @@ jobs:
|
|
|
63
63
|
with_optionals: false
|
|
64
64
|
steps:
|
|
65
65
|
- name: Checkout branch
|
|
66
|
-
uses: actions/checkout@
|
|
66
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
67
67
|
- name: Set up pixi
|
|
68
|
-
uses: prefix-dev/setup-pixi@
|
|
68
|
+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
|
|
69
69
|
with:
|
|
70
70
|
environments: ${{ matrix.environment }}
|
|
71
71
|
# FIXME: Remove when `s3_server` fixture does not start a process anymore
|
|
@@ -79,7 +79,7 @@ jobs:
|
|
|
79
79
|
- name: Run pytest
|
|
80
80
|
run: pixi run -e ${{ matrix.environment }} test-coverage --color=yes ${{ matrix.with_optionals && '-m with_optionals' || '-m "not with_optionals"'}} --cov=dataframely --cov-report=xml
|
|
81
81
|
- name: Upload codecov
|
|
82
|
-
uses: codecov/codecov-action@
|
|
82
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
83
83
|
with:
|
|
84
84
|
files: ./coverage.xml
|
|
85
85
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -13,9 +13,9 @@ jobs:
|
|
|
13
13
|
id-token: write
|
|
14
14
|
steps:
|
|
15
15
|
- name: Checkout branch
|
|
16
|
-
uses: actions/checkout@
|
|
16
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
17
17
|
- name: Set up pixi
|
|
18
|
-
uses: prefix-dev/setup-pixi@
|
|
18
|
+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
|
|
19
19
|
with:
|
|
20
20
|
environments: default
|
|
21
21
|
- name: Install Rust
|
|
@@ -23,9 +23,9 @@ jobs:
|
|
|
23
23
|
os: [ubuntu-latest, windows-latest]
|
|
24
24
|
steps:
|
|
25
25
|
- name: Checkout branch
|
|
26
|
-
uses: actions/checkout@
|
|
26
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
27
27
|
- name: Set up pixi
|
|
28
|
-
uses: prefix-dev/setup-pixi@
|
|
28
|
+
uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
|
|
29
29
|
with:
|
|
30
30
|
environments: nightly
|
|
31
31
|
- name: Install polars nightly
|
|
@@ -16,6 +16,6 @@ jobs:
|
|
|
16
16
|
pull-requests: read
|
|
17
17
|
steps:
|
|
18
18
|
- name: Update release draft
|
|
19
|
-
uses: release-drafter/release-drafter@
|
|
19
|
+
uses: release-drafter/release-drafter@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0
|
|
20
20
|
with:
|
|
21
21
|
token: ${{ github.token }}
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
|
|
36
36
|
steps:
|
|
37
37
|
- name: "Checkout code"
|
|
38
|
-
uses: actions/checkout@
|
|
38
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
39
39
|
with:
|
|
40
40
|
persist-credentials: false
|
|
41
41
|
|
|
@@ -74,6 +74,6 @@ jobs:
|
|
|
74
74
|
# Upload the results to GitHub's code scanning dashboard (optional).
|
|
75
75
|
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
|
76
76
|
- name: "Upload to code-scanning"
|
|
77
|
-
uses: github/codeql-action/upload-sarif@
|
|
77
|
+
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
|
78
78
|
with:
|
|
79
79
|
sarif_file: results.sarif
|
|
@@ -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
|
|
@@ -33,6 +33,8 @@ class Enum(Column):
|
|
|
33
33
|
alias: str | None = None,
|
|
34
34
|
metadata: dict[str, Any] | None = None,
|
|
35
35
|
description: str | None = None,
|
|
36
|
+
sqlalchemy_use_enum: bool = False,
|
|
37
|
+
sqlalchemy_enum_name: str | None = None,
|
|
36
38
|
):
|
|
37
39
|
"""
|
|
38
40
|
Args:
|
|
@@ -68,6 +70,15 @@ class Enum(Column):
|
|
|
68
70
|
names, the specified alias is the only valid name.
|
|
69
71
|
metadata: A dictionary of metadata to attach to the column.
|
|
70
72
|
description: A human-readable description of the column.
|
|
73
|
+
sqlalchemy_use_enum: When ``True``, map this column to :class:`sqlalchemy.Enum`
|
|
74
|
+
in :meth:`~dataframely.Schema.to_sqlalchemy_columns` instead of
|
|
75
|
+
``CHAR`` / ``VARCHAR``.
|
|
76
|
+
sqlalchemy_enum_name: Optional name for the SQLAlchemy / database enum type
|
|
77
|
+
when ``sqlalchemy_use_enum=True``. If omitted and ``categories`` is a
|
|
78
|
+
Python :class:`enum.Enum` subclass, the lowercased enum class is used.
|
|
79
|
+
Otherwise, the name of the column is used.
|
|
80
|
+
The persisted values are the enum members' ``.value`` strings (not
|
|
81
|
+
member names), matching :attr:`categories`.
|
|
71
82
|
"""
|
|
72
83
|
super().__init__(
|
|
73
84
|
nullable=nullable,
|
|
@@ -78,8 +89,26 @@ class Enum(Column):
|
|
|
78
89
|
metadata=metadata,
|
|
79
90
|
description=description,
|
|
80
91
|
)
|
|
92
|
+
if sqlalchemy_enum_name and not sqlalchemy_use_enum:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"`sqlalchemy_enum_name` has no effect when `sqlalchemy_use_enum=False`."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.sqlalchemy_use_enum = sqlalchemy_use_enum
|
|
98
|
+
self.sqlalchemy_enum_name = sqlalchemy_enum_name
|
|
81
99
|
if isclass(categories) and issubclass(categories, enum.Enum):
|
|
100
|
+
# If the user passed an Enum type, we want to determine a default name
|
|
101
|
+
# based on the Enum class name, which is also what sqlalchemy does.
|
|
102
|
+
# One could instead keep a reference to the Enum class around and pass it
|
|
103
|
+
# to sqlalchemy later on, but that will interfere with the base-class implementations
|
|
104
|
+
# of `matches` and `to_dict` / `from_dict`.
|
|
105
|
+
if self.sqlalchemy_use_enum:
|
|
106
|
+
self.sqlalchemy_enum_name = (
|
|
107
|
+
self.sqlalchemy_enum_name or categories.__name__.lower()
|
|
108
|
+
)
|
|
109
|
+
|
|
82
110
|
categories = (item.value for item in categories)
|
|
111
|
+
|
|
83
112
|
self.categories = list(categories)
|
|
84
113
|
|
|
85
114
|
@property
|
|
@@ -92,6 +121,10 @@ class Enum(Column):
|
|
|
92
121
|
return self.categories == dtype.categories.to_list()
|
|
93
122
|
|
|
94
123
|
def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine:
|
|
124
|
+
if self.sqlalchemy_use_enum:
|
|
125
|
+
return sa.Enum(
|
|
126
|
+
*self.categories, name=self.sqlalchemy_enum_name or self._name
|
|
127
|
+
)
|
|
95
128
|
category_lengths = [len(c) for c in self.categories]
|
|
96
129
|
if all(length == category_lengths[0] for length in category_lengths):
|
|
97
130
|
return sa.CHAR(category_lengths[0])
|
|
@@ -9,6 +9,8 @@ import re
|
|
|
9
9
|
|
|
10
10
|
import polars as pl
|
|
11
11
|
|
|
12
|
+
from dataframely._compat import _polars_version_tuple
|
|
13
|
+
|
|
12
14
|
_POLARS_DTYPE_MAP: dict[type[pl.DataType], str] = {
|
|
13
15
|
pl.Boolean: "Bool",
|
|
14
16
|
pl.Int8: "Int8",
|
|
@@ -147,9 +149,16 @@ def _get_dtype_args(dtype: pl.DataType, series: pl.Series) -> list[str]:
|
|
|
147
149
|
return [repr(dtype.categories.to_list())]
|
|
148
150
|
|
|
149
151
|
if isinstance(dtype, pl.List):
|
|
152
|
+
if _polars_version_tuple >= (1, 36, 0):
|
|
153
|
+
return [_dtype_to_column_code(series.explode(empty_as_null=False))]
|
|
150
154
|
return [_dtype_to_column_code(series.explode())]
|
|
151
155
|
|
|
152
156
|
if isinstance(dtype, pl.Array):
|
|
157
|
+
if _polars_version_tuple >= (1, 36, 0):
|
|
158
|
+
return [
|
|
159
|
+
_dtype_to_column_code(series.explode(empty_as_null=False)),
|
|
160
|
+
f"shape={dtype.size}",
|
|
161
|
+
]
|
|
153
162
|
return [_dtype_to_column_code(series.explode()), f"shape={dtype.size}"]
|
|
154
163
|
|
|
155
164
|
if isinstance(dtype, pl.Struct):
|
|
@@ -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
|
# ------------------------------------------------------------------------------------ #
|