dataframely 1.7.6__tar.gz → 1.8.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-1.7.6 → dataframely-1.8.0}/.github/workflows/build.yml +2 -2
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/workflows/chore.yml +2 -2
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/workflows/ci.yml +2 -2
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/workflows/nightly.yml +1 -1
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/workflows/scorecard.yml +1 -1
- {dataframely-1.7.6 → dataframely-1.8.0}/PKG-INFO +2 -2
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/__init__.py +8 -1
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_serialization.py +10 -3
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/collection.py +123 -49
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/__init__.py +2 -0
- dataframely-1.8.0/dataframely/columns/categorical.py +77 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/enum.py +7 -1
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/random.py +1 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/schema.py +7 -3
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/testing/const.py +1 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/pixi.lock +7308 -11938
- {dataframely-1.7.6 → dataframely-1.8.0}/pixi.toml +1 -1
- {dataframely-1.7.6 → dataframely-1.8.0}/pyproject.toml +4 -2
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_read_write_parquet.py +114 -1
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_sample.py +27 -49
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_default_dtypes.py +1 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_pyarrow.py +15 -2
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_sql_schema.py +1 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_read_write_parquet.py +16 -0
- dataframely-1.8.0/tests/test_serialization.py +22 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.copier-answers.yml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.envrc +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.gitattributes +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/CODEOWNERS +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/dependabot.yml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.github/release-drafter.yml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.gitignore +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.pre-commit-config.yaml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.prettierignore +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.prettierrc +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/.readthedocs.yml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/Cargo.lock +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/Cargo.toml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/LICENSE +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/README.md +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/SECURITY.md +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_base_collection.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_base_schema.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_compat.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_deprecation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_extre.pyi +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_filter.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_polars.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_rule.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_typing.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/_validation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/_base.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/_mixins.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/_registry.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/_utils.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/any.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/array.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/bool.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/datetime.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/decimal.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/float.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/integer.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/list.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/object.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/string.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/columns/struct.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/config.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/exc.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/failure.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/functional.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/mypy.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/py.typed +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/testing/__init__.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/testing/factory.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/testing/mask.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/testing/rules.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/dataframely/testing/typing.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docker-compose.yml +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/Makefile +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.collection.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.any.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.bool.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.datetime.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.decimal.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.enum.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.float.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.integer.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.list.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.string.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.columns.struct.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.config.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.exc.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.failure.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.functional.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.mypy.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.random.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.schema.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.testing.const.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.testing.factory.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.testing.mask.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.testing.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.testing.rules.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/dataframely.testing.typing.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_api/modules.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_static/custom.css +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/_static/favicon.ico +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/conf.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/index.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/make.bat +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/sites/development.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/sites/examples/real-world.ipynb +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/sites/faq.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/sites/installation.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/sites/quickstart.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/docs/sites/versioning.rst +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/src/errdefs.rs +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/src/lib.rs +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/src/regex_repr.rs +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/benches/conftest.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/benches/test_collection.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/benches/test_failure.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/benches/test_schema.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_base.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_cast.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_collection_future_annotations.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_create_empty.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_filter_one_to_n.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_filter_validate.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_ignore_in_filter.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_implementation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_matches.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_optional_members.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_repr.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_serialization.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/collection/test_validate_input.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/__init__.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_any.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_array.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_datetime.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_decimal.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_enum.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_float.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_integer.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_list.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_object.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_string.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/column_types/test_struct.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/__init__.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_alias.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_check.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_matches.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_metadata.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_polars_schema.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_rules.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_sample.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_str.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/columns/test_utils.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/core_validation/__init__.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/core_validation/test_column_validation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/core_validation/test_dtype_validation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/core_validation/test_rule_evaluation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/functional/test_concat.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/functional/test_relationships.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_base.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_cast.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_create_empty.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_create_empty_if_none.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_filter.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_inheritance.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_matches.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_repr.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_rule_implementation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_sample.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_serialization.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/schema/test_validate.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_compat.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_config.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_deprecation.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_exc.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_extre.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_failure_info.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_random.py +0 -0
- {dataframely-1.7.6 → dataframely-1.8.0}/tests/test_typing.py +0 -0
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
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@8ca4608ef7f4daeb54f5205b20d0b7cb42f11143 # v0.8.14
|
|
21
21
|
with:
|
|
22
22
|
environments: build
|
|
23
23
|
- name: Set version
|
|
@@ -52,7 +52,7 @@ jobs:
|
|
|
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@8ca4608ef7f4daeb54f5205b20d0b7cb42f11143 # v0.8.14
|
|
56
56
|
with:
|
|
57
57
|
environments: build
|
|
58
58
|
- name: Set version
|
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
GITHUB_TOKEN: ${{ github.token }}
|
|
29
29
|
- name: Post comment about invalid PR title
|
|
30
30
|
if: failure()
|
|
31
|
-
uses: marocchino/sticky-pull-request-comment@
|
|
31
|
+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
32
32
|
with:
|
|
33
33
|
header: conventional-commit-pr-title
|
|
34
34
|
message: |
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
</details>
|
|
46
46
|
- name: Delete comment about invalid PR title
|
|
47
47
|
if: success()
|
|
48
|
-
uses: marocchino/sticky-pull-request-comment@
|
|
48
|
+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
49
49
|
with:
|
|
50
50
|
header: conventional-commit-pr-title
|
|
51
51
|
delete: true
|
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
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@8ca4608ef7f4daeb54f5205b20d0b7cb42f11143 # v0.8.14
|
|
28
28
|
with:
|
|
29
29
|
environments: default lint
|
|
30
30
|
- name: Install repository
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
- name: Checkout branch
|
|
46
46
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
47
47
|
- name: Set up pixi
|
|
48
|
-
uses: prefix-dev/setup-pixi@
|
|
48
|
+
uses: prefix-dev/setup-pixi@8ca4608ef7f4daeb54f5205b20d0b7cb42f11143 # v0.8.14
|
|
49
49
|
with:
|
|
50
50
|
environments: ${{ matrix.environment }}
|
|
51
51
|
- name: Install repository
|
|
@@ -25,7 +25,7 @@ jobs:
|
|
|
25
25
|
- name: Checkout branch
|
|
26
26
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
27
27
|
- name: Set up pixi
|
|
28
|
-
uses: prefix-dev/setup-pixi@
|
|
28
|
+
uses: prefix-dev/setup-pixi@8ca4608ef7f4daeb54f5205b20d0b7cb42f11143 # v0.8.14
|
|
29
29
|
with:
|
|
30
30
|
environments: nightly
|
|
31
31
|
- name: Install polars nightly
|
|
@@ -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@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
|
|
78
78
|
with:
|
|
79
79
|
sarif_file: results.sarif
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataframely
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.10
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.11
|
|
7
7
|
Classifier: Programming Language :: Python :: 3.12
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.13
|
|
9
9
|
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: polars>=1.
|
|
10
|
+
Requires-Dist: polars>=1.32
|
|
11
11
|
Requires-Dist: typing-extensions ; python_full_version < '3.11'
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Summary: A declarative, polars-native data frame validation library
|
|
@@ -15,11 +15,16 @@ from ._base_collection import CollectionMember
|
|
|
15
15
|
from ._filter import filter
|
|
16
16
|
from ._rule import rule
|
|
17
17
|
from ._typing import DataFrame, LazyFrame, Validation
|
|
18
|
-
from .collection import
|
|
18
|
+
from .collection import (
|
|
19
|
+
Collection,
|
|
20
|
+
deserialize_collection,
|
|
21
|
+
read_parquet_metadata_collection,
|
|
22
|
+
)
|
|
19
23
|
from .columns import (
|
|
20
24
|
Any,
|
|
21
25
|
Array,
|
|
22
26
|
Bool,
|
|
27
|
+
Categorical,
|
|
23
28
|
Column,
|
|
24
29
|
Date,
|
|
25
30
|
Datetime,
|
|
@@ -70,8 +75,10 @@ __all__ = [
|
|
|
70
75
|
"Schema",
|
|
71
76
|
"deserialize_schema",
|
|
72
77
|
"read_parquet_metadata_schema",
|
|
78
|
+
"read_parquet_metadata_collection",
|
|
73
79
|
"Any",
|
|
74
80
|
"Bool",
|
|
81
|
+
"Categorical",
|
|
75
82
|
"Column",
|
|
76
83
|
"Date",
|
|
77
84
|
"Datetime",
|
|
@@ -11,6 +11,7 @@ from typing import Any, cast
|
|
|
11
11
|
import polars as pl
|
|
12
12
|
|
|
13
13
|
SCHEMA_METADATA_KEY = "dataframely_schema"
|
|
14
|
+
COLLECTION_METADATA_KEY = "dataframely_collection"
|
|
14
15
|
SERIALIZATION_FORMAT_VERSION = "1"
|
|
15
16
|
|
|
16
17
|
|
|
@@ -45,7 +46,7 @@ class SchemaJSONEncoder(JSONEncoder):
|
|
|
45
46
|
case pl.Expr():
|
|
46
47
|
return {
|
|
47
48
|
"__type__": "expression",
|
|
48
|
-
"value": obj.meta.serialize(
|
|
49
|
+
"value": base64.b64encode(obj.meta.serialize()).decode("utf-8"),
|
|
49
50
|
}
|
|
50
51
|
case pl.LazyFrame():
|
|
51
52
|
return {
|
|
@@ -86,8 +87,14 @@ class SchemaJSONDecoder(JSONDecoder):
|
|
|
86
87
|
case "tuple":
|
|
87
88
|
return tuple(dct["value"])
|
|
88
89
|
case "expression":
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
value_str = cast(str, dct["value"]).encode("utf-8")
|
|
91
|
+
if value_str.startswith(b"{"):
|
|
92
|
+
# NOTE: This branch is for backwards-compatibility only
|
|
93
|
+
data = BytesIO(value_str)
|
|
94
|
+
return pl.Expr.deserialize(data, format="json")
|
|
95
|
+
else:
|
|
96
|
+
data = BytesIO(base64.b64decode(value_str))
|
|
97
|
+
return pl.Expr.deserialize(data)
|
|
91
98
|
case "lazyframe":
|
|
92
99
|
data = BytesIO(
|
|
93
100
|
base64.b64decode(cast(str, dct["value"]).encode("utf-8"))
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# Copyright (c) QuantCo 2025-2025
|
|
2
2
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import json
|
|
5
6
|
import sys
|
|
6
7
|
import warnings
|
|
7
8
|
from abc import ABC
|
|
8
|
-
from collections.abc import Mapping, Sequence
|
|
9
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
9
10
|
from dataclasses import asdict
|
|
11
|
+
from json import JSONDecodeError
|
|
10
12
|
from pathlib import Path
|
|
11
|
-
from typing import Annotated, Any, cast
|
|
13
|
+
from typing import IO, Annotated, Any, cast
|
|
12
14
|
|
|
13
15
|
import polars as pl
|
|
14
16
|
import polars.exceptions as plexc
|
|
@@ -17,6 +19,7 @@ from ._base_collection import BaseCollection, CollectionMember
|
|
|
17
19
|
from ._filter import Filter
|
|
18
20
|
from ._polars import FrameType, join_all_inner, join_all_outer
|
|
19
21
|
from ._serialization import (
|
|
22
|
+
COLLECTION_METADATA_KEY,
|
|
20
23
|
SERIALIZATION_FORMAT_VERSION,
|
|
21
24
|
SchemaJSONDecoder,
|
|
22
25
|
SchemaJSONEncoder,
|
|
@@ -147,9 +150,16 @@ class Collection(BaseCollection, ABC):
|
|
|
147
150
|
A collection where all members (including optional ones) have been sampled
|
|
148
151
|
according to the input parameters.
|
|
149
152
|
|
|
153
|
+
Attention:
|
|
154
|
+
In case the collection has members with a common primary key, the
|
|
155
|
+
`_preprocess_sample` method must return distinct primary key values for each
|
|
156
|
+
sample. The default implementation does this on a best-effort basis but may
|
|
157
|
+
cause primary key violations. Hence, it is recommended to override this
|
|
158
|
+
method and ensure that all primary key columns are set.
|
|
159
|
+
|
|
150
160
|
Raises:
|
|
151
161
|
ValueError: If the :meth:`_preprocess_sample` method does not return all
|
|
152
|
-
common primary
|
|
162
|
+
common primary key columns for all samples.
|
|
153
163
|
ValidationError: If the sampled members violate any of the collection
|
|
154
164
|
filters. If the collection does not have filters, this error is never
|
|
155
165
|
raised. To prevent validation errors, overwrite the
|
|
@@ -247,7 +257,7 @@ class Collection(BaseCollection, ABC):
|
|
|
247
257
|
return cls.validate(members)
|
|
248
258
|
|
|
249
259
|
@classmethod
|
|
250
|
-
def matches(cls, other: type[
|
|
260
|
+
def matches(cls, other: type[Collection]) -> bool:
|
|
251
261
|
"""Check whether this collection semantically matches another.
|
|
252
262
|
|
|
253
263
|
Args:
|
|
@@ -317,9 +327,7 @@ class Collection(BaseCollection, ABC):
|
|
|
317
327
|
of fuzzy sampling rounds when sampling individual members.
|
|
318
328
|
|
|
319
329
|
Args:
|
|
320
|
-
sample: The sample to preprocess.
|
|
321
|
-
Subclasses may decide, however, to introduce a :class:`TypedDict` to
|
|
322
|
-
ease the creation of samples.
|
|
330
|
+
sample: The sample to preprocess.
|
|
323
331
|
index: The index of the sample in the list of samples. Typically, this value
|
|
324
332
|
can be used to assign unique primary keys for samples.
|
|
325
333
|
generator: The generator to use when performing random sampling within the
|
|
@@ -331,10 +339,18 @@ class Collection(BaseCollection, ABC):
|
|
|
331
339
|
primary keys.
|
|
332
340
|
"""
|
|
333
341
|
if len(cls.members()) > 1 and len(cls.common_primary_keys()) > 0:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
)
|
|
342
|
+
# If we have multiple members with a common primary key, we need to ensure
|
|
343
|
+
# that the samples have a value set for all common primary key columns.
|
|
344
|
+
# NOTE: This is experimental as we commit to a primary key that cannot be
|
|
345
|
+
# changed at a later point (e.g. due to primary key violations).
|
|
346
|
+
first_member_columns = next(iter(cls.member_schemas().values())).columns()
|
|
347
|
+
for primary_key in cls.common_primary_keys():
|
|
348
|
+
if primary_key in sample:
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
value = first_member_columns[primary_key].sample(generator).item()
|
|
352
|
+
sample[primary_key] = value
|
|
353
|
+
|
|
338
354
|
return sample
|
|
339
355
|
|
|
340
356
|
# ---------------------------------- VALIDATION ---------------------------------- #
|
|
@@ -638,9 +654,6 @@ class Collection(BaseCollection, ABC):
|
|
|
638
654
|
Each parquet file is named ``<member>.parquet``. No file is written for optional
|
|
639
655
|
members which are not provided in the current collection.
|
|
640
656
|
|
|
641
|
-
In addition, one JSON file named ``schema.json`` is written, serializing the
|
|
642
|
-
collection's definition for fast reads.
|
|
643
|
-
|
|
644
657
|
Args:
|
|
645
658
|
directory: The directory where the Parquet files should be written to. If
|
|
646
659
|
the directory does not exist, it is created automatically, including all
|
|
@@ -661,9 +674,6 @@ class Collection(BaseCollection, ABC):
|
|
|
661
674
|
Each parquet file is named ``<member>.parquet``. No file is written for optional
|
|
662
675
|
members which are not provided in the current collection.
|
|
663
676
|
|
|
664
|
-
In addition, one JSON file named ``schema.json`` is written, serializing the
|
|
665
|
-
collection's definition for fast reads.
|
|
666
|
-
|
|
667
677
|
Args:
|
|
668
678
|
directory: The directory where the Parquet files should be written to. If
|
|
669
679
|
the directory does not exist, it is created automatically, including all
|
|
@@ -680,8 +690,11 @@ class Collection(BaseCollection, ABC):
|
|
|
680
690
|
def _to_parquet(self, directory: str | Path, *, sink: bool, **kwargs: Any) -> None:
|
|
681
691
|
path = Path(directory) if isinstance(directory, str) else directory
|
|
682
692
|
path.mkdir(parents=True, exist_ok=True)
|
|
683
|
-
|
|
684
|
-
|
|
693
|
+
|
|
694
|
+
# The collection schema is serialized as part of the member parquet metadata
|
|
695
|
+
kwargs["metadata"] = kwargs.get("metadata", {}) | {
|
|
696
|
+
COLLECTION_METADATA_KEY: self.serialize()
|
|
697
|
+
}
|
|
685
698
|
|
|
686
699
|
member_schemas = self.member_schemas()
|
|
687
700
|
for key, lf in self.to_dict().items():
|
|
@@ -719,16 +732,16 @@ class Collection(BaseCollection, ABC):
|
|
|
719
732
|
Parquet files may have been written with Hive partitioning.
|
|
720
733
|
validation: The strategy for running validation when reading the data:
|
|
721
734
|
|
|
722
|
-
- ``"allow"`: The method tries to read the
|
|
723
|
-
|
|
735
|
+
- ``"allow"`: The method tries to read the schema data from the parquet
|
|
736
|
+
files. If the stored collection schema matches this collection
|
|
724
737
|
schema, the collection is read without validation. If the stored
|
|
725
|
-
schema mismatches this schema
|
|
726
|
-
the
|
|
727
|
-
``cast=True``.
|
|
738
|
+
schema mismatches this schema no metadata can be found in
|
|
739
|
+
the parquets, or the files have conflicting metadata,
|
|
740
|
+
this method automatically runs :meth:`validate` with ``cast=True``.
|
|
728
741
|
- ``"warn"`: The method behaves similarly to ``"allow"``. However,
|
|
729
742
|
it prints a warning if validation is necessary.
|
|
730
743
|
- ``"forbid"``: The method never runs validation automatically and only
|
|
731
|
-
returns if the
|
|
744
|
+
returns if the metadata stores a collection schema that matches
|
|
732
745
|
this collection.
|
|
733
746
|
- ``"skip"``: The method never runs validation and simply reads the
|
|
734
747
|
data, entrusting the user that the schema is valid. _Use this option
|
|
@@ -747,13 +760,20 @@ class Collection(BaseCollection, ABC):
|
|
|
747
760
|
all required members.
|
|
748
761
|
ValidationError: If the collection cannot be validate.
|
|
749
762
|
|
|
763
|
+
Note:
|
|
764
|
+
This method is backward compatible with older versions of dataframely
|
|
765
|
+
in which the schema metadata was saved to `schema.json` files instead of
|
|
766
|
+
being encoded into the parquet files.
|
|
767
|
+
|
|
750
768
|
Attention:
|
|
751
769
|
Be aware that this method suffers from the same limitations as
|
|
752
770
|
:meth:`serialize`.
|
|
753
771
|
"""
|
|
754
772
|
path = Path(directory)
|
|
755
|
-
data = cls._from_parquet(path, scan=
|
|
756
|
-
if not cls._requires_validation_for_reading_parquets(
|
|
773
|
+
data, collection_type = cls._from_parquet(path, scan=False, **kwargs)
|
|
774
|
+
if not cls._requires_validation_for_reading_parquets(
|
|
775
|
+
path, collection_type, validation
|
|
776
|
+
):
|
|
757
777
|
cls._validate_input_keys(data)
|
|
758
778
|
return cls._init(data)
|
|
759
779
|
return cls.validate(data, cast=True)
|
|
@@ -776,16 +796,16 @@ class Collection(BaseCollection, ABC):
|
|
|
776
796
|
Parquet files may have been written with Hive partitioning.
|
|
777
797
|
validation: The strategy for running validation when reading the data:
|
|
778
798
|
|
|
779
|
-
- ``"allow"`: The method tries to read the
|
|
780
|
-
|
|
799
|
+
- ``"allow"`: The method tries to read the schema data from the parquet
|
|
800
|
+
files. If the stored collection schema matches this collection
|
|
781
801
|
schema, the collection is read without validation. If the stored
|
|
782
|
-
schema mismatches this schema
|
|
783
|
-
the
|
|
784
|
-
``cast=True``.
|
|
802
|
+
schema mismatches this schema no metadata can be found in
|
|
803
|
+
the parquets, or the files have conflicting metadata,
|
|
804
|
+
this method automatically runs :meth:`validate` with ``cast=True``.
|
|
785
805
|
- ``"warn"`: The method behaves similarly to ``"allow"``. However,
|
|
786
806
|
it prints a warning if validation is necessary.
|
|
787
807
|
- ``"forbid"``: The method never runs validation automatically and only
|
|
788
|
-
returns if the
|
|
808
|
+
returns if the metadata stores a collection schema that matches
|
|
789
809
|
this collection.
|
|
790
810
|
- ``"skip"``: The method never runs validation and simply reads the
|
|
791
811
|
data, entrusting the user that the schema is valid. _Use this option
|
|
@@ -808,13 +828,19 @@ class Collection(BaseCollection, ABC):
|
|
|
808
828
|
parquet file into memory if ``"validation"`` is ``"warn"`` or ``"allow"``
|
|
809
829
|
and validation is required.
|
|
810
830
|
|
|
831
|
+
Note: This method is backward compatible with older versions of dataframely
|
|
832
|
+
in which the schema metadata was saved to `schema.json` files instead of
|
|
833
|
+
being encoded into the parquet files.
|
|
834
|
+
|
|
811
835
|
Attention:
|
|
812
836
|
Be aware that this method suffers from the same limitations as
|
|
813
837
|
:meth:`serialize`.
|
|
814
838
|
"""
|
|
815
839
|
path = Path(directory)
|
|
816
|
-
data = cls._from_parquet(path, scan=True, **kwargs)
|
|
817
|
-
if not cls._requires_validation_for_reading_parquets(
|
|
840
|
+
data, collection_type = cls._from_parquet(path, scan=True, **kwargs)
|
|
841
|
+
if not cls._requires_validation_for_reading_parquets(
|
|
842
|
+
path, collection_type, validation
|
|
843
|
+
):
|
|
818
844
|
cls._validate_input_keys(data)
|
|
819
845
|
return cls._init(data)
|
|
820
846
|
return cls.validate(data, cast=True)
|
|
@@ -822,8 +848,9 @@ class Collection(BaseCollection, ABC):
|
|
|
822
848
|
@classmethod
|
|
823
849
|
def _from_parquet(
|
|
824
850
|
cls, path: Path, scan: bool, **kwargs: Any
|
|
825
|
-
) -> dict[str, pl.LazyFrame]:
|
|
851
|
+
) -> tuple[dict[str, pl.LazyFrame], type[Collection] | None]:
|
|
826
852
|
data = {}
|
|
853
|
+
collection_types = set()
|
|
827
854
|
for key in cls.members():
|
|
828
855
|
if (source_path := cls._member_source_path(path, key)) is not None:
|
|
829
856
|
data[key] = (
|
|
@@ -831,7 +858,22 @@ class Collection(BaseCollection, ABC):
|
|
|
831
858
|
if scan
|
|
832
859
|
else pl.read_parquet(source_path, **kwargs).lazy()
|
|
833
860
|
)
|
|
834
|
-
|
|
861
|
+
if source_path.is_file():
|
|
862
|
+
collection_types.add(read_parquet_metadata_collection(source_path))
|
|
863
|
+
else:
|
|
864
|
+
for file in source_path.glob("**/*.parquet"):
|
|
865
|
+
collection_types.add(read_parquet_metadata_collection(file))
|
|
866
|
+
collection_type = _reconcile_collection_types(collection_types)
|
|
867
|
+
|
|
868
|
+
# Backward compatibility: If the parquets do not have schema information,
|
|
869
|
+
# fall back to looking for schema.json
|
|
870
|
+
if (collection_type is None) and (schema_file := path / "schema.json").exists():
|
|
871
|
+
try:
|
|
872
|
+
collection_type = deserialize_collection(schema_file.read_text())
|
|
873
|
+
except (JSONDecodeError, plexc.ComputeError):
|
|
874
|
+
pass
|
|
875
|
+
|
|
876
|
+
return data, collection_type
|
|
835
877
|
|
|
836
878
|
@classmethod
|
|
837
879
|
def _member_source_path(cls, base_path: Path, name: str) -> Path | None:
|
|
@@ -847,27 +889,20 @@ class Collection(BaseCollection, ABC):
|
|
|
847
889
|
def _requires_validation_for_reading_parquets(
|
|
848
890
|
cls,
|
|
849
891
|
directory: Path,
|
|
892
|
+
collection_type: type[Collection] | None,
|
|
850
893
|
validation: Validation,
|
|
851
894
|
) -> bool:
|
|
852
895
|
if validation == "skip":
|
|
853
896
|
return False
|
|
854
897
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
# that the data adheres to the collection and we do not need to run validation.
|
|
858
|
-
if (json_serialization := directory / "schema.json").exists():
|
|
859
|
-
metadata = json_serialization.read_text()
|
|
860
|
-
serialized_collection = deserialize_collection(metadata)
|
|
861
|
-
if cls.matches(serialized_collection):
|
|
862
|
-
return False
|
|
863
|
-
else:
|
|
864
|
-
serialized_collection = None
|
|
898
|
+
if collection_type is not None and cls.matches(collection_type):
|
|
899
|
+
return False
|
|
865
900
|
|
|
866
|
-
#
|
|
901
|
+
# Now we definitely need to run validation. However, we emit different
|
|
867
902
|
# information to the user depending on the value of `validate`.
|
|
868
903
|
msg = (
|
|
869
904
|
"current collection schema does not match stored collection schema"
|
|
870
|
-
if
|
|
905
|
+
if collection_type is not None
|
|
871
906
|
else "no collection schema to check validity can be read from the source"
|
|
872
907
|
)
|
|
873
908
|
if validation == "forbid":
|
|
@@ -903,6 +938,27 @@ class Collection(BaseCollection, ABC):
|
|
|
903
938
|
)
|
|
904
939
|
|
|
905
940
|
|
|
941
|
+
def read_parquet_metadata_collection(
|
|
942
|
+
source: str | Path | IO[bytes] | bytes,
|
|
943
|
+
) -> type[Collection] | None:
|
|
944
|
+
"""Read a dataframely Collection type from the metadata of a parquet file.
|
|
945
|
+
|
|
946
|
+
Args:
|
|
947
|
+
source: Path to a parquet file or a file-like object that contains the metadata.
|
|
948
|
+
|
|
949
|
+
Returns:
|
|
950
|
+
The collection that was serialized to the metadata. ``None`` if no collection
|
|
951
|
+
metadata is found or the deserialization fails.
|
|
952
|
+
"""
|
|
953
|
+
metadata = pl.read_parquet_metadata(source)
|
|
954
|
+
if (schema_metadata := metadata.get(COLLECTION_METADATA_KEY)) is not None:
|
|
955
|
+
try:
|
|
956
|
+
return deserialize_collection(schema_metadata)
|
|
957
|
+
except (JSONDecodeError, plexc.ComputeError):
|
|
958
|
+
return None
|
|
959
|
+
return None
|
|
960
|
+
|
|
961
|
+
|
|
906
962
|
def deserialize_collection(data: str) -> type[Collection]:
|
|
907
963
|
"""Deserialize a collection from a JSON string.
|
|
908
964
|
|
|
@@ -968,3 +1024,21 @@ def _extract_keys_if_exist(
|
|
|
968
1024
|
data: Mapping[str, Any], keys: Sequence[str]
|
|
969
1025
|
) -> dict[str, Any]:
|
|
970
1026
|
return {key: data[key] for key in keys if key in data}
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
def _reconcile_collection_types(
|
|
1030
|
+
collection_types: Iterable[type[Collection] | None],
|
|
1031
|
+
) -> type[Collection] | None:
|
|
1032
|
+
# When reading serialized collections, we may have collection type information from multiple sources
|
|
1033
|
+
# (E.g. one set of information for each parquet file).
|
|
1034
|
+
# This function determines which of them should finally be used
|
|
1035
|
+
if not (collection_types := list(collection_types)):
|
|
1036
|
+
return None
|
|
1037
|
+
if (first_type := collection_types[0]) is None:
|
|
1038
|
+
return None
|
|
1039
|
+
for t in collection_types[1:]:
|
|
1040
|
+
if t is None:
|
|
1041
|
+
return None
|
|
1042
|
+
if not first_type.matches(t):
|
|
1043
|
+
return None
|
|
1044
|
+
return first_type
|
|
@@ -6,6 +6,7 @@ from ._registry import column_from_dict
|
|
|
6
6
|
from .any import Any
|
|
7
7
|
from .array import Array
|
|
8
8
|
from .bool import Bool
|
|
9
|
+
from .categorical import Categorical
|
|
9
10
|
from .datetime import Date, Datetime, Duration, Time
|
|
10
11
|
from .decimal import Decimal
|
|
11
12
|
from .enum import Enum
|
|
@@ -22,6 +23,7 @@ __all__ = [
|
|
|
22
23
|
"Any",
|
|
23
24
|
"Array",
|
|
24
25
|
"Bool",
|
|
26
|
+
"Categorical",
|
|
25
27
|
"Date",
|
|
26
28
|
"Datetime",
|
|
27
29
|
"Decimal",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (c) QuantCo 2025-2025
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import polars as pl
|
|
7
|
+
from sqlalchemy import Dialect
|
|
8
|
+
|
|
9
|
+
from dataframely._compat import pa, sa, sa_TypeEngine
|
|
10
|
+
from dataframely.random import Generator
|
|
11
|
+
|
|
12
|
+
from ._base import Check, Column
|
|
13
|
+
from ._registry import register
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@register
|
|
17
|
+
class Categorical(Column):
|
|
18
|
+
"""A column of categorical (string) values."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
nullable: bool | None = None,
|
|
24
|
+
primary_key: bool = False,
|
|
25
|
+
check: Check | None = None,
|
|
26
|
+
alias: str | None = None,
|
|
27
|
+
metadata: dict[str, Any] | None = None,
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Args:
|
|
31
|
+
nullable: Whether this column may contain null values.
|
|
32
|
+
Explicitly set `nullable=True` if you want your column to be nullable.
|
|
33
|
+
In a future release, `nullable=False` will be the default if `nullable`
|
|
34
|
+
is not specified.
|
|
35
|
+
primary_key: Whether this column is part of the primary key of the schema.
|
|
36
|
+
If ``True``, ``nullable`` is automatically set to ``False``.
|
|
37
|
+
check: A custom rule or multiple rules to run for this column. This can be:
|
|
38
|
+
- A single callable that returns a non-aggregated boolean expression.
|
|
39
|
+
The name of the rule is derived from the callable name, or defaults to
|
|
40
|
+
"check" for lambdas.
|
|
41
|
+
- A list of callables, where each callable returns a non-aggregated
|
|
42
|
+
boolean expression. The name of the rule is derived from the callable
|
|
43
|
+
name, or defaults to "check" for lambdas. Where multiple rules result
|
|
44
|
+
in the same name, the suffix __i is appended to the name.
|
|
45
|
+
- A dictionary mapping rule names to callables, where each callable
|
|
46
|
+
returns a non-aggregated boolean expression.
|
|
47
|
+
All rule names provided here are given the prefix "check_".
|
|
48
|
+
alias: An overwrite for this column's name which allows for using a column
|
|
49
|
+
name that is not a valid Python identifier. Especially note that setting
|
|
50
|
+
this option does _not_ allow to refer to the column with two different
|
|
51
|
+
names, the specified alias is the only valid name.
|
|
52
|
+
metadata: A dictionary of metadata to attach to the column.
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(
|
|
55
|
+
nullable=nullable,
|
|
56
|
+
primary_key=primary_key,
|
|
57
|
+
check=check,
|
|
58
|
+
alias=alias,
|
|
59
|
+
metadata=metadata,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def dtype(self) -> pl.DataType:
|
|
64
|
+
return pl.Categorical()
|
|
65
|
+
|
|
66
|
+
def sqlalchemy_dtype(self, dialect: Dialect) -> sa_TypeEngine:
|
|
67
|
+
return sa.String()
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def pyarrow_dtype(self) -> pa.DataType:
|
|
71
|
+
return pa.dictionary(pa.uint32(), pa.large_string())
|
|
72
|
+
|
|
73
|
+
def _sample_unchecked(self, generator: Generator, n: int) -> pl.Series:
|
|
74
|
+
# We simply sample low-cardinality strings here
|
|
75
|
+
return generator.sample_string(
|
|
76
|
+
n, regex=r"[a-z]{1,2}", null_probability=self._null_probability
|
|
77
|
+
).cast(self.dtype)
|
|
@@ -82,7 +82,13 @@ class Enum(Column):
|
|
|
82
82
|
|
|
83
83
|
@property
|
|
84
84
|
def pyarrow_dtype(self) -> pa.DataType:
|
|
85
|
-
|
|
85
|
+
if len(self.categories) <= 2**8 - 2:
|
|
86
|
+
dtype = pa.uint8()
|
|
87
|
+
elif len(self.categories) <= 2**16 - 2:
|
|
88
|
+
dtype = pa.uint16()
|
|
89
|
+
else:
|
|
90
|
+
dtype = pa.uint32()
|
|
91
|
+
return pa.dictionary(dtype, pa.large_string())
|
|
86
92
|
|
|
87
93
|
def _sample_unchecked(self, generator: Generator, n: int) -> pl.Series:
|
|
88
94
|
return generator.sample_choice(
|
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
import warnings
|
|
9
9
|
from abc import ABC
|
|
10
10
|
from collections.abc import Iterable, Mapping, Sequence
|
|
11
|
+
from json import JSONDecodeError
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import IO, Any, Literal, overload
|
|
13
14
|
|
|
@@ -970,12 +971,15 @@ def read_parquet_metadata_schema(
|
|
|
970
971
|
source: Path to a parquet file or a file-like object that contains the metadata.
|
|
971
972
|
|
|
972
973
|
Returns:
|
|
973
|
-
The schema that was serialized to the metadata
|
|
974
|
-
is found.
|
|
974
|
+
The schema that was serialized to the metadata. ``None`` if no schema metadata
|
|
975
|
+
is found or the deserialization fails.
|
|
975
976
|
"""
|
|
976
977
|
metadata = pl.read_parquet_metadata(source)
|
|
977
978
|
if (schema_metadata := metadata.get(SCHEMA_METADATA_KEY)) is not None:
|
|
978
|
-
|
|
979
|
+
try:
|
|
980
|
+
return deserialize_schema(schema_metadata)
|
|
981
|
+
except (JSONDecodeError, plexc.ComputeError):
|
|
982
|
+
return None
|
|
979
983
|
return None
|
|
980
984
|
|
|
981
985
|
|