dataframely 1.0.0__tar.gz → 1.1.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.
Files changed (160) hide show
  1. {dataframely-1.0.0 → dataframely-1.1.0}/.github/workflows/build.yml +5 -5
  2. {dataframely-1.0.0 → dataframely-1.1.0}/.github/workflows/chore.yml +5 -5
  3. {dataframely-1.0.0 → dataframely-1.1.0}/.github/workflows/ci.yml +3 -2
  4. {dataframely-1.0.0 → dataframely-1.1.0}/.github/workflows/update-lockfiles.yml +2 -2
  5. {dataframely-1.0.0 → dataframely-1.1.0}/PKG-INFO +3 -2
  6. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_base_collection.py +33 -2
  7. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_base_schema.py +2 -2
  8. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_compat.py +1 -1
  9. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_filter.py +1 -1
  10. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_rule.py +2 -2
  11. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/collection.py +11 -4
  12. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/_mixins.py +1 -1
  13. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/decimal.py +3 -1
  14. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/config.py +5 -5
  15. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/exc.py +11 -7
  16. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/failure.py +4 -2
  17. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/mypy.py +7 -6
  18. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/random.py +1 -1
  19. {dataframely-1.0.0 → dataframely-1.1.0}/docs/conf.py +1 -1
  20. {dataframely-1.0.0 → dataframely-1.1.0}/docs/sites/quickstart.rst +1 -1
  21. {dataframely-1.0.0 → dataframely-1.1.0}/pyproject.toml +4 -2
  22. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_base.py +13 -11
  23. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_cast.py +5 -5
  24. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_create_empty.py +1 -1
  25. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_filter_one_to_n.py +1 -1
  26. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_filter_validate.py +8 -8
  27. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_ignore_in_filter.py +3 -3
  28. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_implementation.py +14 -14
  29. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_optional_members.py +3 -3
  30. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_sample.py +71 -7
  31. {dataframely-1.0.0 → dataframely-1.1.0}/tests/collection/test_validate_input.py +2 -2
  32. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_any.py +1 -1
  33. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_datetime.py +12 -6
  34. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_decimal.py +7 -7
  35. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_enum.py +2 -2
  36. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_float.py +19 -17
  37. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_integer.py +14 -12
  38. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_list.py +11 -11
  39. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_string.py +4 -4
  40. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/test_struct.py +8 -8
  41. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_alias.py +3 -3
  42. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_check.py +1 -1
  43. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_default_dtypes.py +1 -1
  44. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_metadata.py +1 -1
  45. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_pyarrow.py +9 -9
  46. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_rules.py +4 -4
  47. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_sample.py +20 -16
  48. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_sql_schema.py +7 -7
  49. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_str.py +4 -4
  50. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_utils.py +5 -5
  51. {dataframely-1.0.0 → dataframely-1.1.0}/tests/core_validation/test_column_validation.py +2 -2
  52. {dataframely-1.0.0 → dataframely-1.1.0}/tests/core_validation/test_dtype_validation.py +4 -4
  53. {dataframely-1.0.0 → dataframely-1.1.0}/tests/core_validation/test_rule_evaluation.py +7 -7
  54. {dataframely-1.0.0 → dataframely-1.1.0}/tests/functional/test_concat.py +2 -2
  55. {dataframely-1.0.0 → dataframely-1.1.0}/tests/functional/test_relationships.py +2 -2
  56. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_base.py +8 -8
  57. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_cast.py +3 -3
  58. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_create_empty.py +2 -2
  59. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_create_empty_if_none.py +2 -2
  60. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_filter.py +11 -7
  61. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_inheritance.py +1 -1
  62. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_rule_implementation.py +4 -4
  63. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_sample.py +11 -11
  64. {dataframely-1.0.0 → dataframely-1.1.0}/tests/schema/test_validate.py +9 -7
  65. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_compat.py +1 -1
  66. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_config.py +4 -4
  67. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_exc.py +3 -3
  68. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_extre.py +9 -9
  69. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_failure_info.py +1 -1
  70. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_random.py +14 -12
  71. {dataframely-1.0.0 → dataframely-1.1.0}/tests/test_typing.py +14 -12
  72. dataframely-1.0.0/tests/schema/__init__.py +0 -2
  73. {dataframely-1.0.0 → dataframely-1.1.0}/.copier-answers.yml +0 -0
  74. {dataframely-1.0.0 → dataframely-1.1.0}/.envrc +0 -0
  75. {dataframely-1.0.0 → dataframely-1.1.0}/.gitattributes +0 -0
  76. {dataframely-1.0.0 → dataframely-1.1.0}/.github/CODEOWNERS +0 -0
  77. {dataframely-1.0.0 → dataframely-1.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  78. {dataframely-1.0.0 → dataframely-1.1.0}/.github/dependabot.yml +0 -0
  79. {dataframely-1.0.0 → dataframely-1.1.0}/.github/release-drafter.yml +0 -0
  80. {dataframely-1.0.0 → dataframely-1.1.0}/.gitignore +0 -0
  81. {dataframely-1.0.0 → dataframely-1.1.0}/.pre-commit-config.yaml +0 -0
  82. {dataframely-1.0.0 → dataframely-1.1.0}/.prettierignore +0 -0
  83. {dataframely-1.0.0 → dataframely-1.1.0}/.prettierrc +0 -0
  84. {dataframely-1.0.0 → dataframely-1.1.0}/.readthedocs.yml +0 -0
  85. {dataframely-1.0.0 → dataframely-1.1.0}/Cargo.lock +0 -0
  86. {dataframely-1.0.0 → dataframely-1.1.0}/Cargo.toml +0 -0
  87. {dataframely-1.0.0 → dataframely-1.1.0}/LICENSE +0 -0
  88. {dataframely-1.0.0 → dataframely-1.1.0}/README.md +0 -0
  89. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/__init__.py +0 -0
  90. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_extre.pyi +0 -0
  91. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_polars.py +0 -0
  92. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_typing.py +0 -0
  93. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/_validation.py +0 -0
  94. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/__init__.py +0 -0
  95. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/_base.py +0 -0
  96. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/_utils.py +0 -0
  97. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/any.py +0 -0
  98. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/bool.py +0 -0
  99. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/datetime.py +0 -0
  100. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/enum.py +0 -0
  101. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/float.py +0 -0
  102. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/integer.py +0 -0
  103. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/list.py +0 -0
  104. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/string.py +0 -0
  105. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/columns/struct.py +0 -0
  106. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/functional.py +0 -0
  107. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/py.typed +0 -0
  108. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/schema.py +0 -0
  109. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/testing/__init__.py +0 -0
  110. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/testing/const.py +0 -0
  111. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/testing/factory.py +0 -0
  112. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/testing/mask.py +0 -0
  113. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/testing/rules.py +0 -0
  114. {dataframely-1.0.0 → dataframely-1.1.0}/dataframely/testing/typing.py +0 -0
  115. {dataframely-1.0.0 → dataframely-1.1.0}/docker-compose.yml +0 -0
  116. {dataframely-1.0.0 → dataframely-1.1.0}/docs/Makefile +0 -0
  117. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.collection.rst +0 -0
  118. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.any.rst +0 -0
  119. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.bool.rst +0 -0
  120. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.datetime.rst +0 -0
  121. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.decimal.rst +0 -0
  122. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.enum.rst +0 -0
  123. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.float.rst +0 -0
  124. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.integer.rst +0 -0
  125. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.list.rst +0 -0
  126. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.rst +0 -0
  127. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.string.rst +0 -0
  128. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.columns.struct.rst +0 -0
  129. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.config.rst +0 -0
  130. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.exc.rst +0 -0
  131. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.failure.rst +0 -0
  132. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.functional.rst +0 -0
  133. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.mypy.rst +0 -0
  134. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.random.rst +0 -0
  135. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.rst +0 -0
  136. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.schema.rst +0 -0
  137. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.testing.const.rst +0 -0
  138. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.testing.factory.rst +0 -0
  139. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.testing.mask.rst +0 -0
  140. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.testing.rst +0 -0
  141. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.testing.rules.rst +0 -0
  142. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/dataframely.testing.typing.rst +0 -0
  143. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_api/modules.rst +0 -0
  144. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_static/custom.css +0 -0
  145. {dataframely-1.0.0 → dataframely-1.1.0}/docs/_static/favicon.ico +0 -0
  146. {dataframely-1.0.0 → dataframely-1.1.0}/docs/index.rst +0 -0
  147. {dataframely-1.0.0 → dataframely-1.1.0}/docs/make.bat +0 -0
  148. {dataframely-1.0.0 → dataframely-1.1.0}/docs/sites/development.rst +0 -0
  149. {dataframely-1.0.0 → dataframely-1.1.0}/docs/sites/examples/real-world.ipynb +0 -0
  150. {dataframely-1.0.0 → dataframely-1.1.0}/docs/sites/faq.rst +0 -0
  151. {dataframely-1.0.0 → dataframely-1.1.0}/docs/sites/installation.rst +0 -0
  152. {dataframely-1.0.0 → dataframely-1.1.0}/pixi.lock +0 -0
  153. {dataframely-1.0.0 → dataframely-1.1.0}/pixi.toml +0 -0
  154. {dataframely-1.0.0 → dataframely-1.1.0}/src/errdefs.rs +0 -0
  155. {dataframely-1.0.0 → dataframely-1.1.0}/src/lib.rs +0 -0
  156. {dataframely-1.0.0 → dataframely-1.1.0}/src/regex_repr.rs +0 -0
  157. {dataframely-1.0.0 → dataframely-1.1.0}/tests/column_types/__init__.py +0 -0
  158. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/__init__.py +0 -0
  159. {dataframely-1.0.0 → dataframely-1.1.0}/tests/columns/test_polars_schema.py +0 -0
  160. {dataframely-1.0.0 → dataframely-1.1.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@0f64e482e3d251f735019b1bc7fb0413ead75b2c # v0.8.2
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
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@0f64e482e3d251f735019b1bc7fb0413ead75b2c # v0.8.2
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
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
- pull_request:
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 == 'pull_request'
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 == 'pull_request' && 'Assign Labels' || 'Draft Release' }}
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 == 'pull_request' && 'Assign labels' || 'Update release draft' }}
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 == 'pull_request' }}
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@0f64e482e3d251f735019b1bc7fb0413ead75b2c # v0.8.2
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@8eaba7c61d661f73d558b0b477156b7b62667fa4
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@0f64e482e3d251f735019b1bc7fb0413ead75b2c # v0.8.2
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@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
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.0.0
3
+ Version: 1.1.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: repository, https://github.com/quantco/dataframely
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 />
@@ -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
@@ -201,6 +231,7 @@ class CollectionMeta(ABCMeta):
201
231
  schema=get_args(kls)[0],
202
232
  is_optional=False,
203
233
  ignored_in_filters=collection_member.ignored_in_filters,
234
+ inline_for_sampling=collection_member.inline_for_sampling,
204
235
  )
205
236
  else:
206
237
  # Some other unknown annotation
@@ -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))
@@ -6,7 +6,7 @@ from typing import Any
6
6
 
7
7
 
8
8
  class _DummyModule: # pragma: no cover
9
- def __init__(self, module: str):
9
+ def __init__(self, module: str) -> None:
10
10
  self.module = module
11
11
 
12
12
  def __getattr__(self, name: str) -> Any:
@@ -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
 
@@ -123,7 +123,10 @@ class Collection(BaseCollection, ABC, Generic[SamplingType]):
123
123
  ...
124
124
  }
125
125
 
126
- _Any_ member/value can be left out and will be sampled automatically.
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
- sample[member] if member in sample else {},
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
@@ -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(value: decimal.Decimal, precision: int | None, scale: int, name: str):
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}`.")
@@ -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__(self, errors: dict[str, tuple[PolarsDataType, PolarsDataType]]):
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__(self, name: str, return_dtype: pl.DataType, is_group_rule: bool):
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` "
@@ -28,7 +28,9 @@ class FailureInfo(Generic[S]):
28
28
  #: The schema used to create the input data frame.
29
29
  schema: type[S]
30
30
 
31
- def __init__(self, lf: pl.LazyFrame, rule_columns: list[str], schema: type[S]):
31
+ def __init__(
32
+ self, lf: pl.LazyFrame, rule_columns: list[str], schema: type[S]
33
+ ) -> None:
32
34
  self._lf = lf
33
35
  self._rule_columns = rule_columns
34
36
  self.schema = schema
@@ -71,7 +73,7 @@ class FailureInfo(Generic[S]):
71
73
 
72
74
  # ---------------------------------- PERSISTENCE --------------------------------- #
73
75
 
74
- def write_parquet(self, file: str | Path | IO[bytes]):
76
+ def write_parquet(self, file: str | Path | IO[bytes]) -> None:
75
77
  """Write the failure info to a Parquet file.
76
78
 
77
79
  Args:
@@ -50,7 +50,7 @@ TYPED_LAZYFRAME_FULLNAME = "dataframely._typing.LazyFrame"
50
50
  # --------------------------------------- RULES -------------------------------------- #
51
51
 
52
52
 
53
- def mark_rules_as_staticmethod(ctx: ClassDefContext):
53
+ def mark_rules_as_staticmethod(ctx: ClassDefContext) -> None:
54
54
  """Mark all methods decorated with `@rule` as `staticmethod`s."""
55
55
  info = ctx.cls.info
56
56
  for sym in info.names.values():
@@ -199,7 +199,7 @@ def _convert_dy_column_to_dtype(
199
199
  def store_typed_dict_type_for_schema(
200
200
  ctx: ClassDefContext,
201
201
  schema_registry: dict[str, TypedDictType],
202
- ):
202
+ ) -> None:
203
203
  """Add `TypedDictType` inferred from the schema's columns to a given registry."""
204
204
 
205
205
  schema_type = ctx.cls.info
@@ -336,11 +336,13 @@ def alter_dataframe_iter_rows_return_type(
336
336
 
337
337
 
338
338
  class DataframelyPlugin(Plugin):
339
- def __init__(self, options: Options):
339
+ def __init__(self, options: Options) -> None:
340
340
  super().__init__(options)
341
341
  self.schema_registry: dict[str, TypedDictType] = {}
342
342
 
343
- def get_base_class_hook(self, fullname: str):
343
+ def get_base_class_hook(
344
+ self, fullname: str
345
+ ) -> Callable[[ClassDefContext], None] | None:
344
346
  # Given a class, check whether it is a subclass of `dy.Schema`. If so, mark
345
347
  # all methods decorated with `@rule` as staticmethods.
346
348
  # Also, store the `TypedDictType` for the schema in a registry to allow downstream
@@ -349,13 +351,12 @@ class DataframelyPlugin(Plugin):
349
351
  if sym and isinstance(sym.node, TypeInfo):
350
352
  if any(base.fullname == SCHEMA_FULLNAME for base in sym.node.mro):
351
353
 
352
- def _hook(ctx: ClassDefContext) -> bool:
354
+ def _hook(ctx: ClassDefContext) -> None:
353
355
  mark_rules_as_staticmethod(ctx)
354
356
  store_typed_dict_type_for_schema(
355
357
  ctx,
356
358
  self.schema_registry,
357
359
  )
358
- return True
359
360
 
360
361
  return _hook
361
362
  return None
@@ -33,7 +33,7 @@ class Generator:
33
33
  seeding.
34
34
  """
35
35
 
36
- def __init__(self, seed: int | None = None):
36
+ def __init__(self, seed: int | None = None) -> None:
37
37
  """
38
38
  Args:
39
39
  seed: The seed to use for initializing the random number generator used
@@ -68,7 +68,7 @@ html_favicon = "_static/favicon.ico"
68
68
  # Copied and adapted from
69
69
  # https://github.com/pandas-dev/pandas/blob/4a14d064187367cacab3ff4652a12a0e45d0711b/doc/source/conf.py#L613-L659
70
70
  # Required configuration function to use sphinx.ext.linkcode
71
- def linkcode_resolve(domain, info):
71
+ def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None:
72
72
  """Determine the URL corresponding to a given Python object."""
73
73
  if domain != "py":
74
74
  return None
@@ -162,7 +162,7 @@ expectations on the schema of the data frame, e.g.:
162
162
 
163
163
  ::
164
164
 
165
- def train_model(df: dy.DataFrame[HouseSchema]):
165
+ def train_model(df: dy.DataFrame[HouseSchema]) -> None:
166
166
  ...
167
167
 
168
168
  The type checker (typically ``mypy``) then ensures that it is actually a
@@ -19,10 +19,11 @@ description = "A declarative, polars-native data frame validation library"
19
19
  name = "dataframely"
20
20
  readme = "README.md"
21
21
  requires-python = ">=3.11"
22
- version = "1.0.0"
22
+ version = "1.1.0"
23
23
 
24
24
  [project.urls]
25
- repository = "https://github.com/quantco/dataframely"
25
+ Repository = "https://github.com/quantco/dataframely"
26
+ Documentation = "https://dataframely.readthedocs.io/"
26
27
 
27
28
  [tool.maturin]
28
29
  module-name = "dataframely._extre"
@@ -66,6 +67,7 @@ quote-style = "double"
66
67
 
67
68
  [tool.mypy]
68
69
  check_untyped_defs = true
70
+ disallow_untyped_defs = true
69
71
  exclude = ["docs/"]
70
72
  explicit_package_bases = true
71
73
  no_implicit_optional = true
@@ -25,32 +25,32 @@ class MyCollection(dy.Collection):
25
25
  second: dy.LazyFrame[MySecondSchema] | None
26
26
 
27
27
 
28
- def test_common_primary_keys():
28
+ def test_common_primary_keys() -> None:
29
29
  assert MyCollection.common_primary_keys() == ["a"]
30
30
 
31
31
 
32
- def test_members():
32
+ def test_members() -> None:
33
33
  members = MyCollection.members()
34
34
  assert not members["first"].is_optional
35
35
  assert members["second"].is_optional
36
36
 
37
37
 
38
- def test_member_schemas():
38
+ def test_member_schemas() -> None:
39
39
  schemas = MyCollection.member_schemas()
40
40
  assert schemas == {"first": MyFirstSchema, "second": MySecondSchema}
41
41
 
42
42
 
43
- def test_required_members():
43
+ def test_required_members() -> None:
44
44
  required_members = MyCollection.required_members()
45
45
  assert required_members == {"first"}
46
46
 
47
47
 
48
- def test_optional_members():
48
+ def test_optional_members() -> None:
49
49
  optional_members = MyCollection.optional_members()
50
50
  assert optional_members == {"second"}
51
51
 
52
52
 
53
- def test_cast():
53
+ def test_cast() -> None:
54
54
  collection = MyCollection.cast(
55
55
  {
56
56
  "first": pl.LazyFrame({"a": [1, 2, 3]}),
@@ -74,7 +74,7 @@ def test_cast():
74
74
  {"first": pl.LazyFrame({"a": [1, 2, 3]}, schema={"a": pl.UInt8})},
75
75
  ],
76
76
  )
77
- def test_to_dict(expected: dict[str, pl.LazyFrame]):
77
+ def test_to_dict(expected: dict[str, pl.LazyFrame]) -> None:
78
78
  collection = MyCollection.validate(expected)
79
79
 
80
80
  # Check that export looks as expected
@@ -87,7 +87,7 @@ def test_to_dict(expected: dict[str, pl.LazyFrame]):
87
87
  assert MyCollection.is_valid(observed)
88
88
 
89
89
 
90
- def test_collect_all():
90
+ def test_collect_all() -> None:
91
91
  collection = MyCollection.cast(
92
92
  {
93
93
  "first": pl.LazyFrame({"a": [1, 2, 3]}).filter(pl.col("a") < 3),
@@ -106,7 +106,7 @@ def test_collect_all():
106
106
  assert len(out.second.collect()) == 2
107
107
 
108
108
 
109
- def test_collect_all_optional():
109
+ def test_collect_all_optional() -> None:
110
110
  collection = MyCollection.cast({"first": pl.LazyFrame({"a": [1, 2, 3]})})
111
111
  out = collection.collect_all()
112
112
 
@@ -118,7 +118,9 @@ def test_collect_all_optional():
118
118
  @pytest.mark.parametrize(
119
119
  "read_fn", [MyCollection.scan_parquet, MyCollection.read_parquet]
120
120
  )
121
- def test_read_write_parquet(tmp_path: Path, read_fn: Callable[[Path], MyCollection]):
121
+ def test_read_write_parquet(
122
+ tmp_path: Path, read_fn: Callable[[Path], MyCollection]
123
+ ) -> None:
122
124
  collection = MyCollection.cast(
123
125
  {
124
126
  "first": pl.LazyFrame({"a": [1, 2, 3]}),
@@ -139,7 +141,7 @@ def test_read_write_parquet(tmp_path: Path, read_fn: Callable[[Path], MyCollecti
139
141
  )
140
142
  def test_read_write_parquet_optional(
141
143
  tmp_path: Path, read_fn: Callable[[Path], MyCollection]
142
- ):
144
+ ) -> None:
143
145
  collection = MyCollection.cast({"first": pl.LazyFrame({"a": [1, 2, 3]})})
144
146
  collection.write_parquet(tmp_path)
145
147