dataframely 1.0.0__tar.gz → 1.2.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.0.0 → dataframely-1.2.0}/.github/workflows/build.yml +5 -5
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/workflows/chore.yml +5 -5
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/workflows/ci.yml +3 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/workflows/update-lockfiles.yml +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/PKG-INFO +3 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/__init__.py +2 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_base_collection.py +85 -55
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_base_schema.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_compat.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_filter.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_rule.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/collection.py +14 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/__init__.py +2 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/_mixins.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/decimal.py +3 -1
- dataframely-1.2.0/dataframely/columns/object.py +63 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/config.py +5 -5
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/exc.py +11 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/failure.py +4 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/functional.py +7 -3
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/mypy.py +7 -6
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/random.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/conf.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/sites/quickstart.rst +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/pyproject.toml +4 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_base.py +13 -11
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_cast.py +5 -5
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_create_empty.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_filter_one_to_n.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_filter_validate.py +8 -8
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_ignore_in_filter.py +3 -3
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_implementation.py +32 -14
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_optional_members.py +3 -3
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_sample.py +93 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/collection/test_validate_input.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_any.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_datetime.py +12 -6
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_decimal.py +7 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_enum.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_float.py +19 -17
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_integer.py +14 -12
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_list.py +11 -11
- dataframely-1.2.0/tests/column_types/test_object.py +60 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_string.py +4 -4
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/test_struct.py +8 -8
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_alias.py +3 -3
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_check.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_default_dtypes.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_metadata.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_pyarrow.py +9 -9
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_rules.py +4 -4
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_sample.py +20 -16
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_sql_schema.py +15 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_str.py +4 -4
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_utils.py +5 -5
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/core_validation/test_column_validation.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/core_validation/test_dtype_validation.py +4 -4
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/core_validation/test_rule_evaluation.py +7 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/functional/test_concat.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/functional/test_relationships.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_base.py +8 -8
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_cast.py +3 -3
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_create_empty.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_create_empty_if_none.py +2 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_filter.py +11 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_inheritance.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_rule_implementation.py +4 -4
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_sample.py +11 -11
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/schema/test_validate.py +9 -7
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_compat.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_config.py +4 -4
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_exc.py +3 -3
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_extre.py +9 -9
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_failure_info.py +1 -1
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_random.py +14 -12
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/test_typing.py +36 -14
- dataframely-1.0.0/tests/schema/__init__.py +0 -2
- {dataframely-1.0.0 → dataframely-1.2.0}/.copier-answers.yml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.envrc +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.gitattributes +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/CODEOWNERS +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/dependabot.yml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.github/release-drafter.yml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.gitignore +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.pre-commit-config.yaml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.prettierignore +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.prettierrc +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/.readthedocs.yml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/Cargo.lock +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/Cargo.toml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/LICENSE +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/README.md +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_extre.pyi +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_polars.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_typing.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/_validation.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/_base.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/_utils.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/any.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/bool.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/datetime.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/enum.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/float.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/integer.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/list.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/string.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/columns/struct.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/py.typed +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/schema.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/testing/__init__.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/testing/const.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/testing/factory.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/testing/mask.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/testing/rules.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/dataframely/testing/typing.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docker-compose.yml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/Makefile +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.collection.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.any.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.bool.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.datetime.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.decimal.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.enum.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.float.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.integer.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.list.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.string.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.columns.struct.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.config.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.exc.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.failure.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.functional.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.mypy.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.random.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.schema.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.testing.const.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.testing.factory.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.testing.mask.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.testing.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.testing.rules.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/dataframely.testing.typing.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_api/modules.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_static/custom.css +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/_static/favicon.ico +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/index.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/make.bat +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/sites/development.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/sites/examples/real-world.ipynb +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/sites/faq.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/docs/sites/installation.rst +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/pixi.lock +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/pixi.toml +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/src/errdefs.rs +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/src/lib.rs +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/src/regex_repr.rs +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/column_types/__init__.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/__init__.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/columns/test_polars_schema.py +0 -0
- {dataframely-1.0.0 → dataframely-1.2.0}/tests/core_validation/__init__.py +0 -0
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
with:
|
|
16
16
|
fetch-depth: 0
|
|
17
17
|
- name: Set up pixi
|
|
18
|
-
uses: prefix-dev/setup-pixi@
|
|
18
|
+
uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8
|
|
19
19
|
with:
|
|
20
20
|
environments: build
|
|
21
21
|
- name: Set version
|
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
- name: Build project
|
|
24
24
|
run: pixi run -e build build-sdist
|
|
25
25
|
- name: Upload package
|
|
26
|
-
uses: actions/upload-artifact@
|
|
26
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
27
27
|
with:
|
|
28
28
|
name: sdist
|
|
29
29
|
path: dist/*
|
|
@@ -50,7 +50,7 @@ jobs:
|
|
|
50
50
|
with:
|
|
51
51
|
fetch-depth: 0
|
|
52
52
|
- name: Set up pixi
|
|
53
|
-
uses: prefix-dev/setup-pixi@
|
|
53
|
+
uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8
|
|
54
54
|
with:
|
|
55
55
|
environments: build
|
|
56
56
|
- name: Set version
|
|
@@ -64,7 +64,7 @@ jobs:
|
|
|
64
64
|
- name: Check package
|
|
65
65
|
run: pixi run -e build check-wheel
|
|
66
66
|
- name: Upload package
|
|
67
|
-
uses: actions/upload-artifact@
|
|
67
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
68
68
|
with:
|
|
69
69
|
name: wheel-${{ matrix.target-platform }}
|
|
70
70
|
path: dist/*
|
|
@@ -78,7 +78,7 @@ jobs:
|
|
|
78
78
|
id-token: write
|
|
79
79
|
environment: pypi
|
|
80
80
|
steps:
|
|
81
|
-
- uses: actions/download-artifact@
|
|
81
|
+
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
|
82
82
|
with:
|
|
83
83
|
path: dist
|
|
84
84
|
merge-multiple: true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Chore
|
|
2
2
|
on:
|
|
3
|
-
|
|
3
|
+
pull_request_target:
|
|
4
4
|
branches: [main]
|
|
5
5
|
types: [opened, reopened, edited, synchronize]
|
|
6
6
|
push:
|
|
@@ -13,7 +13,7 @@ concurrency:
|
|
|
13
13
|
jobs:
|
|
14
14
|
check-pr-title:
|
|
15
15
|
name: Check PR Title
|
|
16
|
-
if: github.event_name == '
|
|
16
|
+
if: github.event_name == 'pull_request_target'
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
permissions:
|
|
19
19
|
contents: read
|
|
@@ -51,16 +51,16 @@ jobs:
|
|
|
51
51
|
delete: true
|
|
52
52
|
|
|
53
53
|
release-drafter:
|
|
54
|
-
name: ${{ github.event_name == '
|
|
54
|
+
name: ${{ github.event_name == 'pull_request_target' && 'Assign Labels' || 'Draft Release' }}
|
|
55
55
|
runs-on: ubuntu-latest
|
|
56
56
|
permissions:
|
|
57
57
|
contents: write
|
|
58
58
|
pull-requests: write
|
|
59
59
|
steps:
|
|
60
|
-
- name: ${{ github.event_name == '
|
|
60
|
+
- name: ${{ github.event_name == 'pull_request_target' && 'Assign labels' || 'Update release draft' }}
|
|
61
61
|
uses: release-drafter/release-drafter@v6
|
|
62
62
|
with:
|
|
63
|
-
disable-releaser: ${{ github.event_name == '
|
|
63
|
+
disable-releaser: ${{ github.event_name == 'pull_request_target' }}
|
|
64
64
|
disable-autolabeler: ${{ github.event_name == 'push' }}
|
|
65
65
|
env:
|
|
66
66
|
GITHUB_TOKEN: ${{ github.token }}
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
# needed for 'pre-commit-mirrors-insert-license'
|
|
22
22
|
fetch-depth: 0
|
|
23
23
|
- name: Set up pixi
|
|
24
|
-
uses: prefix-dev/setup-pixi@
|
|
24
|
+
uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8
|
|
25
25
|
with:
|
|
26
26
|
environments: default lint
|
|
27
27
|
- name: Install repository
|
|
@@ -42,7 +42,7 @@ jobs:
|
|
|
42
42
|
- name: Checkout branch
|
|
43
43
|
uses: actions/checkout@v4
|
|
44
44
|
- name: Set up pixi
|
|
45
|
-
uses: prefix-dev/setup-pixi@
|
|
45
|
+
uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8
|
|
46
46
|
with:
|
|
47
47
|
environments: ${{ matrix.environment }}
|
|
48
48
|
- name: Install repository
|
|
@@ -53,3 +53,4 @@ jobs:
|
|
|
53
53
|
uses: codecov/codecov-action@v5
|
|
54
54
|
with:
|
|
55
55
|
files: ./coverage.xml
|
|
56
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -15,13 +15,13 @@ jobs:
|
|
|
15
15
|
steps:
|
|
16
16
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
17
17
|
- name: Set up pixi
|
|
18
|
-
uses: prefix-dev/setup-pixi@
|
|
18
|
+
uses: prefix-dev/setup-pixi@19eac09b398e3d0c747adc7921926a6d802df4da # v0.8.8
|
|
19
19
|
with:
|
|
20
20
|
run-install: false
|
|
21
21
|
- name: Update lockfiles
|
|
22
22
|
run: pixi update --json --no-install | pixi exec pixi-diff-to-markdown >> diff.md
|
|
23
23
|
- name: Create pull request
|
|
24
|
-
uses: peter-evans/create-pull-request@
|
|
24
|
+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
|
25
25
|
with:
|
|
26
26
|
token: ${{ github.token }}
|
|
27
27
|
commit-message: Update pixi lockfile
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataframely
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.11
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -12,7 +12,8 @@ Summary: A declarative, polars-native data frame validation library
|
|
|
12
12
|
Author-email: Andreas Albert <andreas.albert@quantco.com>, Daniel Elsner <daniel.elsner@quantco.com>, Oliver Borchert <oliver.borchert@quantco.com>
|
|
13
13
|
Requires-Python: >=3.11
|
|
14
14
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
15
|
-
Project-URL:
|
|
15
|
+
Project-URL: Repository, https://github.com/quantco/dataframely
|
|
16
|
+
Project-URL: Documentation, https://dataframely.readthedocs.io/
|
|
16
17
|
|
|
17
18
|
<!-- LOGO -->
|
|
18
19
|
<br />
|
|
@@ -7,7 +7,7 @@ import typing
|
|
|
7
7
|
from abc import ABCMeta
|
|
8
8
|
from collections.abc import Iterable
|
|
9
9
|
from dataclasses import dataclass, field
|
|
10
|
-
from typing import Annotated, Any, Self, get_args, get_origin
|
|
10
|
+
from typing import Annotated, Any, Self, cast, get_args, get_origin
|
|
11
11
|
|
|
12
12
|
import polars as pl
|
|
13
13
|
|
|
@@ -48,6 +48,12 @@ class CollectionMember:
|
|
|
48
48
|
|
|
49
49
|
#: Whether the member should be ignored in the filter method.
|
|
50
50
|
ignored_in_filters: bool = False
|
|
51
|
+
#: Whether the member's non-primary key columns should be inlined for sampling.
|
|
52
|
+
#: This means that value overrides are supplied on the top-level rather than in
|
|
53
|
+
#: a subkey with the member's name. Only valid if the member's primary key matches
|
|
54
|
+
#: the collection's common primary key. Two members that share common column names
|
|
55
|
+
#: may not both be inlined for sampling.
|
|
56
|
+
inline_for_sampling: bool = False
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
# --------------------------------------- UTILS -------------------------------------- #
|
|
@@ -79,7 +85,7 @@ class Metadata:
|
|
|
79
85
|
members: dict[str, MemberInfo] = field(default_factory=dict)
|
|
80
86
|
filters: dict[str, Filter] = field(default_factory=dict)
|
|
81
87
|
|
|
82
|
-
def update(self, other: Self):
|
|
88
|
+
def update(self, other: Self) -> None:
|
|
83
89
|
self.members.update(other.members)
|
|
84
90
|
self.filters.update(other.filters)
|
|
85
91
|
|
|
@@ -92,7 +98,7 @@ class CollectionMeta(ABCMeta):
|
|
|
92
98
|
namespace: dict[str, Any],
|
|
93
99
|
*args: Any,
|
|
94
100
|
**kwargs: Any,
|
|
95
|
-
):
|
|
101
|
+
) -> CollectionMeta:
|
|
96
102
|
result = Metadata()
|
|
97
103
|
for base in bases:
|
|
98
104
|
result.update(mcs._get_metadata_recursively(base))
|
|
@@ -136,6 +142,30 @@ class CollectionMeta(ABCMeta):
|
|
|
136
142
|
f"{len(intersection)} such filters: {sorted(intersection)}."
|
|
137
143
|
)
|
|
138
144
|
|
|
145
|
+
# 3) Check that inlining for sampling is configured correctly.
|
|
146
|
+
if len(non_ignored_member_schemas) > 0:
|
|
147
|
+
common_primary_keys = _common_primary_keys(non_ignored_member_schemas)
|
|
148
|
+
inlined_columns: set[str] = set()
|
|
149
|
+
for member, info in result.members.items():
|
|
150
|
+
if info.inline_for_sampling:
|
|
151
|
+
if set(info.schema.primary_keys()) != common_primary_keys:
|
|
152
|
+
raise ImplementationError(
|
|
153
|
+
f"Member '{member}' is inlined for sampling but its primary "
|
|
154
|
+
"key is a superset of the common primary key. Such a member "
|
|
155
|
+
"must not be inlined to be able to provide multiple values "
|
|
156
|
+
"for a single combination of the common primary key."
|
|
157
|
+
)
|
|
158
|
+
non_primary_key_columns = (
|
|
159
|
+
set(info.schema.column_names()) - common_primary_keys
|
|
160
|
+
)
|
|
161
|
+
if len(inlined_columns & non_primary_key_columns):
|
|
162
|
+
raise ImplementationError(
|
|
163
|
+
f"At least one column name of member '{member}' clashes "
|
|
164
|
+
"with a column name of another member that is inlined for "
|
|
165
|
+
"sampling."
|
|
166
|
+
)
|
|
167
|
+
inlined_columns.update(non_primary_key_columns)
|
|
168
|
+
|
|
139
169
|
return super().__new__(mcs, name, bases, namespace, *args, **kwargs)
|
|
140
170
|
|
|
141
171
|
@staticmethod
|
|
@@ -153,58 +183,9 @@ class CollectionMeta(ABCMeta):
|
|
|
153
183
|
# Get all members via the annotations
|
|
154
184
|
if "__annotations__" in source:
|
|
155
185
|
for attr, kls in source["__annotations__"].items():
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
collection_member = CollectionMember()
|
|
160
|
-
|
|
161
|
-
if origin is Annotated:
|
|
162
|
-
annotation_args = get_args(kls)
|
|
163
|
-
origin_arg0 = get_origin(annotation_args[0])
|
|
164
|
-
if not origin_arg0 or not issubclass(origin_arg0, TypedLazyFrame):
|
|
165
|
-
raise AnnotationImplementationError(attr, kls)
|
|
166
|
-
if len(annotation_args) > 2:
|
|
167
|
-
raise AnnotationImplementationError(attr, kls)
|
|
168
|
-
if not isinstance(annotation_args[1], CollectionMember):
|
|
169
|
-
raise AnnotationImplementationError(attr, kls)
|
|
170
|
-
|
|
171
|
-
# Continue with wrapped FrameType
|
|
172
|
-
collection_member = annotation_args[1]
|
|
173
|
-
kls = annotation_args[0]
|
|
174
|
-
origin = origin_arg0
|
|
175
|
-
|
|
176
|
-
if origin is None:
|
|
177
|
-
# `None` annotation is not allowed
|
|
178
|
-
raise AnnotationImplementationError(attr, kls)
|
|
179
|
-
elif origin == typing.Union:
|
|
180
|
-
# Happy path: optional member
|
|
181
|
-
union_args = get_args(kls)
|
|
182
|
-
if len(union_args) != 2:
|
|
183
|
-
raise AnnotationImplementationError(attr, kls)
|
|
184
|
-
if not any(get_origin(arg) is None for arg in union_args):
|
|
185
|
-
raise AnnotationImplementationError(attr, kls)
|
|
186
|
-
|
|
187
|
-
[not_none_arg] = [
|
|
188
|
-
arg for arg in union_args if get_origin(arg) is not None
|
|
189
|
-
]
|
|
190
|
-
if not issubclass(get_origin(not_none_arg), TypedLazyFrame):
|
|
191
|
-
raise AnnotationImplementationError(attr, kls)
|
|
192
|
-
|
|
193
|
-
result.members[attr] = MemberInfo(
|
|
194
|
-
schema=get_args(not_none_arg)[0],
|
|
195
|
-
is_optional=True,
|
|
196
|
-
ignored_in_filters=collection_member.ignored_in_filters,
|
|
197
|
-
)
|
|
198
|
-
elif issubclass(origin, TypedLazyFrame):
|
|
199
|
-
# Happy path: required member
|
|
200
|
-
result.members[attr] = MemberInfo(
|
|
201
|
-
schema=get_args(kls)[0],
|
|
202
|
-
is_optional=False,
|
|
203
|
-
ignored_in_filters=collection_member.ignored_in_filters,
|
|
204
|
-
)
|
|
205
|
-
else:
|
|
206
|
-
# Some other unknown annotation
|
|
207
|
-
raise AnnotationImplementationError(attr, kls)
|
|
186
|
+
result.members[attr] = CollectionMeta._derive_member_info(
|
|
187
|
+
attr, kls, CollectionMember()
|
|
188
|
+
)
|
|
208
189
|
|
|
209
190
|
# Get all filters by traversing the source
|
|
210
191
|
for attr, value in {
|
|
@@ -215,6 +196,55 @@ class CollectionMeta(ABCMeta):
|
|
|
215
196
|
|
|
216
197
|
return result
|
|
217
198
|
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _derive_member_info(
|
|
201
|
+
attr: str, type_annotation: Any, collection_member: CollectionMember
|
|
202
|
+
) -> MemberInfo:
|
|
203
|
+
origin = get_origin(type_annotation)
|
|
204
|
+
|
|
205
|
+
if origin is None:
|
|
206
|
+
# `None` annotation is not allowed
|
|
207
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
208
|
+
elif origin == Annotated:
|
|
209
|
+
# Maybe happy path: annotated member, dispatch recursively
|
|
210
|
+
annotation_args = cast(list[Any], get_args(type_annotation))
|
|
211
|
+
if len(annotation_args) > 2:
|
|
212
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
213
|
+
if not isinstance(annotation_args[1], CollectionMember):
|
|
214
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
215
|
+
return CollectionMeta._derive_member_info(
|
|
216
|
+
attr, annotation_args[0], annotation_args[1]
|
|
217
|
+
)
|
|
218
|
+
elif origin == typing.Union:
|
|
219
|
+
# Happy path: optional member
|
|
220
|
+
union_args = get_args(type_annotation)
|
|
221
|
+
if len(union_args) != 2:
|
|
222
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
223
|
+
if not any(get_origin(arg) is None for arg in union_args):
|
|
224
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
225
|
+
|
|
226
|
+
[not_none_arg] = [arg for arg in union_args if get_origin(arg) is not None]
|
|
227
|
+
if not issubclass(get_origin(not_none_arg), TypedLazyFrame):
|
|
228
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
229
|
+
|
|
230
|
+
return MemberInfo(
|
|
231
|
+
schema=get_args(not_none_arg)[0],
|
|
232
|
+
is_optional=True,
|
|
233
|
+
ignored_in_filters=collection_member.ignored_in_filters,
|
|
234
|
+
inline_for_sampling=collection_member.inline_for_sampling,
|
|
235
|
+
)
|
|
236
|
+
elif issubclass(origin, TypedLazyFrame):
|
|
237
|
+
# Happy path: required member
|
|
238
|
+
return MemberInfo(
|
|
239
|
+
schema=get_args(type_annotation)[0],
|
|
240
|
+
is_optional=False,
|
|
241
|
+
ignored_in_filters=collection_member.ignored_in_filters,
|
|
242
|
+
inline_for_sampling=collection_member.inline_for_sampling,
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
# Some other unknown annotation
|
|
246
|
+
raise AnnotationImplementationError(attr, type_annotation)
|
|
247
|
+
|
|
218
248
|
|
|
219
249
|
class BaseCollection(metaclass=CollectionMeta):
|
|
220
250
|
"""Internal utility abstraction to reference collections without introducing
|
|
@@ -58,7 +58,7 @@ class Metadata:
|
|
|
58
58
|
columns: dict[str, Column] = field(default_factory=dict)
|
|
59
59
|
rules: dict[str, Rule] = field(default_factory=dict)
|
|
60
60
|
|
|
61
|
-
def update(self, other: Self):
|
|
61
|
+
def update(self, other: Self) -> None:
|
|
62
62
|
self.columns.update(other.columns)
|
|
63
63
|
self.rules.update(other.rules)
|
|
64
64
|
|
|
@@ -71,7 +71,7 @@ class SchemaMeta(ABCMeta):
|
|
|
71
71
|
namespace: dict[str, Any],
|
|
72
72
|
*args: Any,
|
|
73
73
|
**kwargs: Any,
|
|
74
|
-
):
|
|
74
|
+
) -> SchemaMeta:
|
|
75
75
|
result = Metadata()
|
|
76
76
|
for base in bases:
|
|
77
77
|
result.update(mcs._get_metadata_recursively(base))
|
|
@@ -12,7 +12,7 @@ C = TypeVar("C")
|
|
|
12
12
|
class Filter(Generic[C]):
|
|
13
13
|
"""Internal class representing logic for filtering members of a collection."""
|
|
14
14
|
|
|
15
|
-
def __init__(self, logic: Callable[[C], pl.LazyFrame]):
|
|
15
|
+
def __init__(self, logic: Callable[[C], pl.LazyFrame]) -> None:
|
|
16
16
|
self.logic = logic
|
|
17
17
|
|
|
18
18
|
|
|
@@ -12,14 +12,14 @@ ValidationFunction = Callable[[], pl.Expr]
|
|
|
12
12
|
class Rule:
|
|
13
13
|
"""Internal class representing validation rules."""
|
|
14
14
|
|
|
15
|
-
def __init__(self, expr: pl.Expr):
|
|
15
|
+
def __init__(self, expr: pl.Expr) -> None:
|
|
16
16
|
self.expr = expr
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class GroupRule(Rule):
|
|
20
20
|
"""Rule that is evaluated on a group of columns."""
|
|
21
21
|
|
|
22
|
-
def __init__(self, expr: pl.Expr, group_columns: list[str]):
|
|
22
|
+
def __init__(self, expr: pl.Expr, group_columns: list[str]) -> None:
|
|
23
23
|
super().__init__(expr)
|
|
24
24
|
self.group_columns = group_columns
|
|
25
25
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import sys
|
|
5
5
|
import warnings
|
|
6
6
|
from abc import ABC
|
|
7
|
-
from collections.abc import Mapping,
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any, Generic, Self, TypeVar, cast
|
|
10
10
|
|
|
@@ -20,10 +20,10 @@ from .random import Generator
|
|
|
20
20
|
|
|
21
21
|
if sys.version_info >= (3, 13):
|
|
22
22
|
SamplingType = TypeVar(
|
|
23
|
-
"SamplingType", bound=
|
|
23
|
+
"SamplingType", bound=Mapping[str, Any], default=Mapping[str, Any]
|
|
24
24
|
)
|
|
25
25
|
else: # pragma: no cover
|
|
26
|
-
SamplingType = TypeVar("SamplingType", bound=
|
|
26
|
+
SamplingType = TypeVar("SamplingType", bound=Mapping[str, Any])
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Collection(BaseCollection, ABC, Generic[SamplingType]):
|
|
@@ -123,7 +123,10 @@ class Collection(BaseCollection, ABC, Generic[SamplingType]):
|
|
|
123
123
|
...
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
*Any* member/value can be left out and will be sampled automatically.
|
|
127
|
+
Note that overrides for columns of members that are annotated with
|
|
128
|
+
``inline_for_sampling=True`` can be supplied on the top-level instead
|
|
129
|
+
of in a nested dictionary.
|
|
127
130
|
generator: The (seeded) generator to use for sampling data. If ``None``, a
|
|
128
131
|
generator with random seed is automatically created.
|
|
129
132
|
|
|
@@ -198,7 +201,11 @@ class Collection(BaseCollection, ABC, Generic[SamplingType]):
|
|
|
198
201
|
else _extract_keys_if_exist(sample, primary_keys)
|
|
199
202
|
),
|
|
200
203
|
**_extract_keys_if_exist(
|
|
201
|
-
|
|
204
|
+
(
|
|
205
|
+
sample
|
|
206
|
+
if member_infos[member].inline_for_sampling
|
|
207
|
+
else (sample[member] if member in sample else {})
|
|
208
|
+
),
|
|
202
209
|
schema.column_names(),
|
|
203
210
|
),
|
|
204
211
|
}
|
|
@@ -498,7 +505,7 @@ class Collection(BaseCollection, ABC, Generic[SamplingType]):
|
|
|
498
505
|
|
|
499
506
|
# ---------------------------------- PERSISTENCE --------------------------------- #
|
|
500
507
|
|
|
501
|
-
def write_parquet(self, directory: Path):
|
|
508
|
+
def write_parquet(self, directory: Path) -> None:
|
|
502
509
|
"""Write the members of this collection to Parquet files in a directory.
|
|
503
510
|
|
|
504
511
|
This method writes one Parquet file per member into the provided directory.
|
|
@@ -590,7 +597,7 @@ class Collection(BaseCollection, ABC, Generic[SamplingType]):
|
|
|
590
597
|
return out
|
|
591
598
|
|
|
592
599
|
@classmethod
|
|
593
|
-
def _validate_input_keys(cls, data: Mapping[str, FrameType], /):
|
|
600
|
+
def _validate_input_keys(cls, data: Mapping[str, FrameType], /) -> None:
|
|
594
601
|
actual = set(data)
|
|
595
602
|
|
|
596
603
|
missing = cls.required_members() - actual
|
|
@@ -10,6 +10,7 @@ from .enum import Enum
|
|
|
10
10
|
from .float import Float, Float32, Float64
|
|
11
11
|
from .integer import Int8, Int16, Int32, Int64, Integer, UInt8, UInt16, UInt32, UInt64
|
|
12
12
|
from .list import List
|
|
13
|
+
from .object import Object
|
|
13
14
|
from .string import String
|
|
14
15
|
from .struct import Struct
|
|
15
16
|
|
|
@@ -31,6 +32,7 @@ __all__ = [
|
|
|
31
32
|
"Int32",
|
|
32
33
|
"Int64",
|
|
33
34
|
"Integer",
|
|
35
|
+
"Object",
|
|
34
36
|
"UInt8",
|
|
35
37
|
"UInt16",
|
|
36
38
|
"UInt32",
|
|
@@ -83,7 +83,7 @@ U = TypeVar("U")
|
|
|
83
83
|
class IsInMixin(Generic[U], Base):
|
|
84
84
|
"""Mixin to use for types implementing "is in"."""
|
|
85
85
|
|
|
86
|
-
def __init__(self, *, is_in: Sequence[U] | None = None, **kwargs: Any):
|
|
86
|
+
def __init__(self, *, is_in: Sequence[U] | None = None, **kwargs: Any) -> None:
|
|
87
87
|
super().__init__(**kwargs)
|
|
88
88
|
self.is_in = is_in
|
|
89
89
|
|
|
@@ -148,7 +148,9 @@ class Decimal(OrdinalMixin[decimal.Decimal], Column):
|
|
|
148
148
|
# --------------------------------------- UTILS -------------------------------------- #
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
def _validate(
|
|
151
|
+
def _validate(
|
|
152
|
+
value: decimal.Decimal, precision: int | None, scale: int, name: str
|
|
153
|
+
) -> None:
|
|
152
154
|
exponent = value.as_tuple().exponent
|
|
153
155
|
if not isinstance(exponent, int):
|
|
154
156
|
raise ValueError(f"Encountered 'inf' or 'NaN' for `{name}`.")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright (c) QuantCo 2025-2025
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import polars as pl
|
|
10
|
+
|
|
11
|
+
from dataframely._compat import pa, sa, sa_TypeEngine
|
|
12
|
+
from dataframely.random import Generator
|
|
13
|
+
|
|
14
|
+
from ._base import Column
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Object(Column):
|
|
18
|
+
"""A Python Object column."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
nullable: bool = True,
|
|
24
|
+
primary_key: bool = False,
|
|
25
|
+
check: Callable[[pl.Expr], pl.Expr] | 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
|
+
primary_key: Whether this column is part of the primary key of the schema.
|
|
33
|
+
check: A custom check to run for this column. Must return a non-aggregated
|
|
34
|
+
boolean expression.
|
|
35
|
+
alias: An overwrite for this column's name which allows for using a column
|
|
36
|
+
name that is not a valid Python identifier. Especially note that setting
|
|
37
|
+
this option does _not_ allow to refer to the column with two different
|
|
38
|
+
names, the specified alias is the only valid name.
|
|
39
|
+
metadata: A dictionary of metadata to attach to the column.
|
|
40
|
+
"""
|
|
41
|
+
super().__init__(
|
|
42
|
+
nullable=nullable,
|
|
43
|
+
primary_key=primary_key,
|
|
44
|
+
check=check,
|
|
45
|
+
alias=alias,
|
|
46
|
+
metadata=metadata,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def dtype(self) -> pl.DataType:
|
|
51
|
+
return pl.Object()
|
|
52
|
+
|
|
53
|
+
def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine:
|
|
54
|
+
raise NotImplementedError("SQL column cannot have 'Object' type.")
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def pyarrow_dtype(self) -> pa.DataType:
|
|
58
|
+
raise NotImplementedError("PyArrow column cannot have 'Object' type.")
|
|
59
|
+
|
|
60
|
+
def _sample_unchecked(self, generator: Generator, n: int) -> pl.Series:
|
|
61
|
+
raise NotImplementedError(
|
|
62
|
+
"Random data sampling not implemented for 'Object' type."
|
|
63
|
+
)
|
|
@@ -25,23 +25,23 @@ class Config(contextlib.ContextDecorator):
|
|
|
25
25
|
#: Singleton stack to track where to go back after exiting a context.
|
|
26
26
|
_stack: list[Options] = []
|
|
27
27
|
|
|
28
|
-
def __init__(self, **options: Unpack[Options]):
|
|
28
|
+
def __init__(self, **options: Unpack[Options]) -> None:
|
|
29
29
|
self._local_options: Options = {**default_options(), **options}
|
|
30
30
|
|
|
31
31
|
@staticmethod
|
|
32
|
-
def set_max_sampling_iterations(iterations: int):
|
|
32
|
+
def set_max_sampling_iterations(iterations: int) -> None:
|
|
33
33
|
"""Set the maximum number of sampling iterations to use on
|
|
34
34
|
:meth:`Schema.sample`."""
|
|
35
35
|
Config.options["max_sampling_iterations"] = iterations
|
|
36
36
|
|
|
37
37
|
@staticmethod
|
|
38
|
-
def restore_defaults():
|
|
38
|
+
def restore_defaults() -> None:
|
|
39
39
|
"""Restore the defaults of the configuration."""
|
|
40
40
|
Config.options = default_options()
|
|
41
41
|
|
|
42
42
|
# ------------------------------------ CONTEXT ----------------------------------- #
|
|
43
43
|
|
|
44
|
-
def __enter__(self):
|
|
44
|
+
def __enter__(self) -> None:
|
|
45
45
|
Config._stack.append(Config.options)
|
|
46
46
|
Config.options = self._local_options
|
|
47
47
|
|
|
@@ -50,5 +50,5 @@ class Config(contextlib.ContextDecorator):
|
|
|
50
50
|
exc_type: type[BaseException] | None,
|
|
51
51
|
exc_val: BaseException | None,
|
|
52
52
|
exc_tb: TracebackType | None,
|
|
53
|
-
):
|
|
53
|
+
) -> None:
|
|
54
54
|
Config.options = Config._stack.pop()
|
|
@@ -11,7 +11,7 @@ from ._polars import PolarsDataType
|
|
|
11
11
|
class ValidationError(Exception):
|
|
12
12
|
"""Error raised when :mod:`dataframely` validation encounters an issue."""
|
|
13
13
|
|
|
14
|
-
def __init__(self, message: str):
|
|
14
|
+
def __init__(self, message: str) -> None:
|
|
15
15
|
super().__init__()
|
|
16
16
|
self.message = message
|
|
17
17
|
|
|
@@ -22,7 +22,9 @@ class ValidationError(Exception):
|
|
|
22
22
|
class DtypeValidationError(ValidationError):
|
|
23
23
|
"""Validation error raised when column dtypes are wrong."""
|
|
24
24
|
|
|
25
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self, errors: dict[str, tuple[PolarsDataType, PolarsDataType]]
|
|
27
|
+
) -> None:
|
|
26
28
|
super().__init__(f"{len(errors)} columns have an invalid dtype")
|
|
27
29
|
self.errors = errors
|
|
28
30
|
|
|
@@ -37,7 +39,7 @@ class DtypeValidationError(ValidationError):
|
|
|
37
39
|
class RuleValidationError(ValidationError):
|
|
38
40
|
"""Complex validation error raised when rule validation fails."""
|
|
39
41
|
|
|
40
|
-
def __init__(self, errors: dict[str, int]):
|
|
42
|
+
def __init__(self, errors: dict[str, int]) -> None:
|
|
41
43
|
super().__init__(f"{len(errors)} rules failed validation")
|
|
42
44
|
|
|
43
45
|
# Split into schema errors and column errors
|
|
@@ -75,11 +77,11 @@ class RuleValidationError(ValidationError):
|
|
|
75
77
|
class MemberValidationError(ValidationError):
|
|
76
78
|
"""Validation error raised when multiple members of a collection fail validation."""
|
|
77
79
|
|
|
78
|
-
def __init__(self, errors: dict[str, ValidationError]):
|
|
80
|
+
def __init__(self, errors: dict[str, ValidationError]) -> None:
|
|
79
81
|
super().__init__(f"{len(errors)} members failed validation")
|
|
80
82
|
self.errors = errors
|
|
81
83
|
|
|
82
|
-
def __str__(self):
|
|
84
|
+
def __str__(self) -> str:
|
|
83
85
|
details = [
|
|
84
86
|
f" > Member '{name}' failed validation:\n"
|
|
85
87
|
+ "\n".join(" " + line for line in str(error).split("\n"))
|
|
@@ -95,7 +97,7 @@ class ImplementationError(Exception):
|
|
|
95
97
|
class AnnotationImplementationError(ImplementationError):
|
|
96
98
|
"""Error raised when the annotations of a collection are invalid."""
|
|
97
99
|
|
|
98
|
-
def __init__(self, attr: str, kls: type):
|
|
100
|
+
def __init__(self, attr: str, kls: type) -> None:
|
|
99
101
|
message = (
|
|
100
102
|
"Annotations of a 'dy.Collection' may only be an (optional) "
|
|
101
103
|
f"'dy.LazyFrame', but \"{attr}\" has type '{kls}'."
|
|
@@ -106,7 +108,9 @@ class AnnotationImplementationError(ImplementationError):
|
|
|
106
108
|
class RuleImplementationError(ImplementationError):
|
|
107
109
|
"""Error raised when a rule is implemented incorrectly."""
|
|
108
110
|
|
|
109
|
-
def __init__(
|
|
111
|
+
def __init__(
|
|
112
|
+
self, name: str, return_dtype: pl.DataType, is_group_rule: bool
|
|
113
|
+
) -> None:
|
|
110
114
|
if is_group_rule:
|
|
111
115
|
details = (
|
|
112
116
|
" When implementing a group rule (i.e. when using the `group_by` "
|