dataframely 2.11.0__tar.gz → 2.13.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 (233) hide show
  1. {dataframely-2.11.0 → dataframely-2.13.0}/PKG-INFO +1 -1
  2. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_polars.py +14 -5
  3. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/collection.py +141 -36
  4. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/filter_result.py +11 -0
  5. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/functional.py +2 -2
  6. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/schema.py +21 -13
  7. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/operations.rst +1 -0
  8. {dataframely-2.11.0 → dataframely-2.13.0}/pyproject.toml +1 -1
  9. {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/test_collection.py +12 -6
  10. dataframely-2.13.0/tests/collection/test_pipe.py +55 -0
  11. dataframely-2.13.0/tests/collection/test_skip_member_validation.py +108 -0
  12. {dataframely-2.11.0 → dataframely-2.13.0}/.claude/CLAUDE.md +0 -0
  13. {dataframely-2.11.0 → dataframely-2.13.0}/.copier-answers.yml +0 -0
  14. {dataframely-2.11.0 → dataframely-2.13.0}/.envrc +0 -0
  15. {dataframely-2.11.0 → dataframely-2.13.0}/.gitattributes +0 -0
  16. {dataframely-2.11.0 → dataframely-2.13.0}/.github/CODEOWNERS +0 -0
  17. {dataframely-2.11.0 → dataframely-2.13.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {dataframely-2.11.0 → dataframely-2.13.0}/.github/copilot-instructions.md +0 -0
  19. {dataframely-2.11.0 → dataframely-2.13.0}/.github/dependabot.yml +0 -0
  20. {dataframely-2.11.0 → dataframely-2.13.0}/.github/instructions/tests.instructions.md +0 -0
  21. {dataframely-2.11.0 → dataframely-2.13.0}/.github/release-drafter.yml +0 -0
  22. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/build.yml +0 -0
  23. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/chore.yml +0 -0
  24. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/ci.yml +0 -0
  25. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/copilot-setup-steps.yml +0 -0
  26. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/nightly.yml +0 -0
  27. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/release-drafter.yml +0 -0
  28. {dataframely-2.11.0 → dataframely-2.13.0}/.github/workflows/scorecard.yml +0 -0
  29. {dataframely-2.11.0 → dataframely-2.13.0}/.gitignore +0 -0
  30. {dataframely-2.11.0 → dataframely-2.13.0}/.lefthook.yaml +0 -0
  31. {dataframely-2.11.0 → dataframely-2.13.0}/.prettierignore +0 -0
  32. {dataframely-2.11.0 → dataframely-2.13.0}/.prettierrc +0 -0
  33. {dataframely-2.11.0 → dataframely-2.13.0}/.readthedocs.yml +0 -0
  34. {dataframely-2.11.0 → dataframely-2.13.0}/AGENTS.md +0 -0
  35. {dataframely-2.11.0 → dataframely-2.13.0}/Cargo.lock +0 -0
  36. {dataframely-2.11.0 → dataframely-2.13.0}/Cargo.toml +0 -0
  37. {dataframely-2.11.0 → dataframely-2.13.0}/LICENSE +0 -0
  38. {dataframely-2.11.0 → dataframely-2.13.0}/README.md +0 -0
  39. {dataframely-2.11.0 → dataframely-2.13.0}/SECURITY.md +0 -0
  40. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/__init__.py +0 -0
  41. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_base_schema.py +0 -0
  42. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_compat.py +0 -0
  43. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_deprecation.py +0 -0
  44. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_filter.py +0 -0
  45. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_match_to_schema.py +0 -0
  46. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_native.pyi +0 -0
  47. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_plugin.py +0 -0
  48. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_pydantic.py +0 -0
  49. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_rule.py +0 -0
  50. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_serialization.py +0 -0
  51. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/__init__.py +0 -0
  52. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/_base.py +0 -0
  53. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/_exc.py +0 -0
  54. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/_fsspec.py +0 -0
  55. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/constants.py +0 -0
  56. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/delta.py +0 -0
  57. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_storage/parquet.py +0 -0
  58. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/_typing.py +0 -0
  59. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/__init__.py +0 -0
  60. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/_base.py +0 -0
  61. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/collection/filter_result.py +0 -0
  62. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/__init__.py +0 -0
  63. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_base.py +0 -0
  64. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_mixins.py +0 -0
  65. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_registry.py +0 -0
  66. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/_utils.py +0 -0
  67. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/any.py +0 -0
  68. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/array.py +0 -0
  69. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/binary.py +0 -0
  70. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/bool.py +0 -0
  71. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/categorical.py +0 -0
  72. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/datetime.py +0 -0
  73. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/decimal.py +0 -0
  74. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/enum.py +0 -0
  75. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/float.py +0 -0
  76. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/integer.py +0 -0
  77. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/list.py +0 -0
  78. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/object.py +0 -0
  79. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/string.py +0 -0
  80. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/columns/struct.py +0 -0
  81. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/config.py +0 -0
  82. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/exc.py +0 -0
  83. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/experimental/__init__.py +0 -0
  84. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/experimental/infer_schema.py +0 -0
  85. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/py.typed +0 -0
  86. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/random.py +0 -0
  87. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/__init__.py +0 -0
  88. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/const.py +0 -0
  89. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/factory.py +0 -0
  90. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/mask.py +0 -0
  91. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/rules.py +0 -0
  92. {dataframely-2.11.0 → dataframely-2.13.0}/dataframely/testing/storage.py +0 -0
  93. {dataframely-2.11.0 → dataframely-2.13.0}/docker-compose.yml +0 -0
  94. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_static/custom.css +0 -0
  95. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_static/favicon.ico +0 -0
  96. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/autosummary/class.rst +0 -0
  97. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/autosummary/method.rst +0 -0
  98. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/classes/column.rst +0 -0
  99. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/classes/error.rst +0 -0
  100. {dataframely-2.11.0 → dataframely-2.13.0}/docs/_templates/classes/filter_result.rst +0 -0
  101. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/generation.rst +0 -0
  102. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/index.rst +0 -0
  103. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/io.rst +0 -0
  104. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/metadata.rst +0 -0
  105. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/collection/validation.rst +0 -0
  106. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/columns/index.rst +0 -0
  107. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/errors/index.rst +0 -0
  108. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/experimental/index.rst +0 -0
  109. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/filter_result/failure_info.rst +0 -0
  110. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/filter_result/index.rst +0 -0
  111. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/index.rst +0 -0
  112. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/misc/index.rst +0 -0
  113. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/conversion.rst +0 -0
  114. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/generation.rst +0 -0
  115. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/index.rst +0 -0
  116. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/io.rst +0 -0
  117. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/metadata.rst +0 -0
  118. {dataframely-2.11.0 → dataframely-2.13.0}/docs/api/schema/validation.rst +0 -0
  119. {dataframely-2.11.0 → dataframely-2.13.0}/docs/conf.py +0 -0
  120. {dataframely-2.11.0 → dataframely-2.13.0}/docs/css/custom.css +0 -0
  121. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/coding-agents.md +0 -0
  122. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/development.md +0 -0
  123. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/examples/index.md +0 -0
  124. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/examples/real-world.ipynb +0 -0
  125. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/faq.md +0 -0
  126. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/column-metadata.md +0 -0
  127. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/data-generation.md +0 -0
  128. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/index.md +0 -0
  129. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/lazy-validation.md +0 -0
  130. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/primary-keys.md +0 -0
  131. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/serialization.md +0 -0
  132. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/features/sql-generation.md +0 -0
  133. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/index.md +0 -0
  134. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/migration/index.md +0 -0
  135. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/migration/v1-v2.md +0 -0
  136. {dataframely-2.11.0 → dataframely-2.13.0}/docs/guides/quickstart.md +0 -0
  137. {dataframely-2.11.0 → dataframely-2.13.0}/docs/index.md +0 -0
  138. {dataframely-2.11.0 → dataframely-2.13.0}/pixi.lock +0 -0
  139. {dataframely-2.11.0 → dataframely-2.13.0}/pixi.toml +0 -0
  140. {dataframely-2.11.0 → dataframely-2.13.0}/rust-toolchain.toml +0 -0
  141. {dataframely-2.11.0 → dataframely-2.13.0}/skills/SKILL.md +0 -0
  142. {dataframely-2.11.0 → dataframely-2.13.0}/src/lib.rs +0 -0
  143. {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/mod.rs +0 -0
  144. {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/rule_failure.rs +0 -0
  145. {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/utils.rs +0 -0
  146. {dataframely-2.11.0 → dataframely-2.13.0}/src/polars_plugin/validation_error.rs +0 -0
  147. {dataframely-2.11.0 → dataframely-2.13.0}/src/regex/errdefs.rs +0 -0
  148. {dataframely-2.11.0 → dataframely-2.13.0}/src/regex/mod.rs +0 -0
  149. {dataframely-2.11.0 → dataframely-2.13.0}/src/regex/repr.rs +0 -0
  150. {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/conftest.py +0 -0
  151. {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/test_failure.py +0 -0
  152. {dataframely-2.11.0 → dataframely-2.13.0}/tests/benches/test_schema.py +0 -0
  153. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_base.py +0 -0
  154. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_cast.py +0 -0
  155. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_collection_future_annotations.py +0 -0
  156. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_create_empty.py +0 -0
  157. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_dataframe_members.py +0 -0
  158. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_filter_one_to_n.py +0 -0
  159. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_filter_validate.py +0 -0
  160. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_ignore_in_filter.py +0 -0
  161. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_implementation.py +0 -0
  162. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_join.py +0 -0
  163. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_matches.py +0 -0
  164. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_optional_members.py +0 -0
  165. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_propagate_row_failures.py +0 -0
  166. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_repr.py +0 -0
  167. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_sample.py +0 -0
  168. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_serialization.py +0 -0
  169. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_storage.py +0 -0
  170. {dataframely-2.11.0 → dataframely-2.13.0}/tests/collection/test_validate_input.py +0 -0
  171. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/__init__.py +0 -0
  172. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_any.py +0 -0
  173. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_array.py +0 -0
  174. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_binary.py +0 -0
  175. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_datetime.py +0 -0
  176. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_decimal.py +0 -0
  177. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_enum.py +0 -0
  178. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_float.py +0 -0
  179. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_integer.py +0 -0
  180. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_list.py +0 -0
  181. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_object.py +0 -0
  182. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_string.py +0 -0
  183. {dataframely-2.11.0 → dataframely-2.13.0}/tests/column_types/test_struct.py +0 -0
  184. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/__init__.py +0 -0
  185. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_alias.py +0 -0
  186. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_base.py +0 -0
  187. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_check.py +0 -0
  188. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_default_dtypes.py +0 -0
  189. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_description.py +0 -0
  190. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_matches.py +0 -0
  191. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_metadata.py +0 -0
  192. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_polars_schema.py +0 -0
  193. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_pyarrow.py +0 -0
  194. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_pydantic.py +0 -0
  195. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_rules.py +0 -0
  196. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_sample.py +0 -0
  197. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_sqlalchemy_columns.py +0 -0
  198. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_str.py +0 -0
  199. {dataframely-2.11.0 → dataframely-2.13.0}/tests/columns/test_utils.py +0 -0
  200. {dataframely-2.11.0 → dataframely-2.13.0}/tests/conftest.py +0 -0
  201. {dataframely-2.11.0 → dataframely-2.13.0}/tests/core_validation/__init__.py +0 -0
  202. {dataframely-2.11.0 → dataframely-2.13.0}/tests/core_validation/test_match_to_schema.py +0 -0
  203. {dataframely-2.11.0 → dataframely-2.13.0}/tests/core_validation/test_rule_evaluation.py +0 -0
  204. {dataframely-2.11.0 → dataframely-2.13.0}/tests/experimental/test_infer_schema.py +0 -0
  205. {dataframely-2.11.0 → dataframely-2.13.0}/tests/failure_info/test_storage.py +0 -0
  206. {dataframely-2.11.0 → dataframely-2.13.0}/tests/functional/test_concat.py +0 -0
  207. {dataframely-2.11.0 → dataframely-2.13.0}/tests/functional/test_relationships.py +0 -0
  208. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_base.py +0 -0
  209. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_cast.py +0 -0
  210. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_create_empty.py +0 -0
  211. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_create_empty_if_none.py +0 -0
  212. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_filter.py +0 -0
  213. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_inheritance.py +0 -0
  214. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_matches.py +0 -0
  215. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_pydantic_model.py +0 -0
  216. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_read_write_parquet.py +0 -0
  217. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_repr.py +0 -0
  218. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_rule_implementation.py +0 -0
  219. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_sample.py +0 -0
  220. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_serialization.py +0 -0
  221. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_storage.py +0 -0
  222. {dataframely-2.11.0 → dataframely-2.13.0}/tests/schema/test_validate.py +0 -0
  223. {dataframely-2.11.0 → dataframely-2.13.0}/tests/storage/test_delta.py +0 -0
  224. {dataframely-2.11.0 → dataframely-2.13.0}/tests/storage/test_fsspec.py +0 -0
  225. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_compat.py +0 -0
  226. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_config.py +0 -0
  227. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_deprecation.py +0 -0
  228. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_factory.py +0 -0
  229. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_native_regex.py +0 -0
  230. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_pydantic.py +0 -0
  231. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_random.py +0 -0
  232. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_serialization.py +0 -0
  233. {dataframely-2.11.0 → dataframely-2.13.0}/tests/test_typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframely
3
- Version: 2.11.0
3
+ Version: 2.13.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.10
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: BSD-3-Clause
3
3
 
4
4
  import datetime as dt
5
- from typing import TypeVar
5
+ from typing import Any, TypeVar
6
6
 
7
7
  import polars as pl
8
8
  from polars.datatypes import DataTypeClass
@@ -41,9 +41,18 @@ def timedelta_matches_resolution(d: dt.timedelta, resolution: str) -> bool:
41
41
  return datetime_matches_resolution(EPOCH_DATETIME + d, resolution)
42
42
 
43
43
 
44
- def collect_if(lf: pl.LazyFrame, condition: bool) -> pl.DataFrame | pl.LazyFrame:
45
- """Collect a lazy frame if the original was eager, otherwise return the lazy
46
- frame."""
44
+ def collect_if(lf: pl.LazyFrame, condition: bool, **kwargs: Any) -> pl.LazyFrame:
45
+ """Collect a lazy frame based on `condition`."""
47
46
  if condition:
48
- return lf.collect()
47
+ return lf.collect(**kwargs).lazy()
49
48
  return lf
49
+
50
+
51
+ def collect_all_if(
52
+ lfs: dict[str, pl.LazyFrame], condition: bool, **kwargs: Any
53
+ ) -> dict[str, pl.LazyFrame]:
54
+ """Collect the lazy frames in the dictionary based on `condition`."""
55
+ if condition:
56
+ dfs = pl.collect_all(lfs.values(), **kwargs)
57
+ return {k: v.lazy() for k, v in zip(lfs.keys(), dfs)}
58
+ return lfs
@@ -8,11 +8,21 @@ import sys
8
8
  import textwrap
9
9
  import warnings
10
10
  from abc import ABC
11
- from collections.abc import Iterable, Mapping, Sequence
11
+ from collections.abc import Callable, Iterable, Mapping, Sequence
12
12
  from dataclasses import asdict
13
13
  from json import JSONDecodeError
14
14
  from pathlib import Path
15
- from typing import IO, Annotated, Any, Literal, cast, overload
15
+ from typing import (
16
+ IO,
17
+ Annotated,
18
+ Any,
19
+ Concatenate,
20
+ Literal,
21
+ ParamSpec,
22
+ TypeVar,
23
+ cast,
24
+ overload,
25
+ )
16
26
 
17
27
  import polars as pl
18
28
  import polars.exceptions as plexc
@@ -21,7 +31,7 @@ from dataframely._compat import deltalake
21
31
  from dataframely._filter import Filter
22
32
  from dataframely._native import format_rule_failures
23
33
  from dataframely._plugin import all_rules_required
24
- from dataframely._polars import FrameType, collect_if
34
+ from dataframely._polars import FrameType, collect_all_if
25
35
  from dataframely._serialization import (
26
36
  SERIALIZATION_FORMAT_VERSION,
27
37
  SchemaJSONDecoder,
@@ -53,6 +63,9 @@ else:
53
63
 
54
64
  _FILTER_COLUMN_PREFIX = "__DATAFRAMELY_FILTER_COLUMN__"
55
65
 
66
+ P = ParamSpec("P")
67
+ T = TypeVar("T")
68
+
56
69
 
57
70
  class Collection(BaseCollection, ABC):
58
71
  """Base class for all collections of data frames with a predefined schema.
@@ -367,7 +380,14 @@ class Collection(BaseCollection, ABC):
367
380
 
368
381
  @classmethod
369
382
  def validate(
370
- cls, data: Mapping[str, FrameType], /, *, cast: bool = False, eager: bool = True
383
+ cls,
384
+ data: Mapping[str, FrameType],
385
+ /,
386
+ *,
387
+ cast: bool = False,
388
+ eager: bool = True,
389
+ skip_member_validation: bool = False,
390
+ **kwargs: Any,
371
391
  ) -> Self:
372
392
  """Validate that a set of data frames satisfy the collection's invariants.
373
393
 
@@ -387,6 +407,13 @@ class Collection(BaseCollection, ABC):
387
407
  :meth:`~polars.LazyFrame.collect` on the individual member or
388
408
  :meth:`collect_all` on the collection. Note that, in the latter case,
389
409
  information from error messages is limited.
410
+ skip_member_validation: Whether to skip validating individual members and only
411
+ apply the collection filters. **Use this option with caution** as it
412
+ requires the caller to ensure that the individual members have been
413
+ validated. This option is particularly useful in performance-critical
414
+ scenarios where the members are known to be valid.
415
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
416
+ :meth:`polars.LazyFrame.collect` when `eager=True`.
390
417
 
391
418
  Raises:
392
419
  ValueError: If an insufficient set of input data frames is provided, i.e. if
@@ -408,7 +435,13 @@ class Collection(BaseCollection, ABC):
408
435
  if eager:
409
436
  # If we perform the validation eagerly, we call filter and check the failure
410
437
  # information to properly construct a useful error message.
411
- filtered, failures = cls.filter(data, cast=cast, eager=True)
438
+ filtered, failures = cls.filter(
439
+ data,
440
+ cast=cast,
441
+ eager=True,
442
+ skip_member_validation=skip_member_validation,
443
+ **kwargs,
444
+ )
412
445
  if any(len(failure) > 0 for failure in failures.values()):
413
446
  errors: dict[str, str] = {}
414
447
  for member, failure in failures.items():
@@ -439,7 +472,17 @@ class Collection(BaseCollection, ABC):
439
472
  # efficiently as we cannot easily propagate error messages from different
440
473
  # members anyways.
441
474
  members: dict[str, pl.LazyFrame] = {
442
- name: member.schema.validate(data[name].lazy(), cast=cast, eager=False)
475
+ name: (
476
+ (
477
+ member.schema.cast(data[name].lazy())
478
+ if cast
479
+ else data[name].lazy()
480
+ )
481
+ if skip_member_validation
482
+ else member.schema.validate(
483
+ data[name].lazy(), cast=cast, eager=False
484
+ )
485
+ )
443
486
  for name, member in cls.members().items()
444
487
  if name in data
445
488
  }
@@ -476,7 +519,9 @@ class Collection(BaseCollection, ABC):
476
519
  return cls._init(members)
477
520
 
478
521
  @classmethod
479
- def is_valid(cls, data: Mapping[str, FrameType], /, *, cast: bool = False) -> bool:
522
+ def is_valid(
523
+ cls, data: Mapping[str, FrameType], /, *, cast: bool = False, **kwargs: Any
524
+ ) -> bool:
480
525
  """Utility method to check whether :meth:`validate` raises an exception.
481
526
 
482
527
  Args:
@@ -485,6 +530,8 @@ class Collection(BaseCollection, ABC):
485
530
  the member as key.
486
531
  cast: Whether columns with a wrong data type in the member data frame are
487
532
  cast to their schemas' defined data types if possible.
533
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
534
+ :meth:`polars.LazyFrame.collect`.
488
535
 
489
536
  Returns:
490
537
  Whether the provided members satisfy the invariants of the collection.
@@ -499,7 +546,7 @@ class Collection(BaseCollection, ABC):
499
546
  members: dict[str, pl.LazyFrame] = {}
500
547
  for member, schema in cls.member_schemas().items():
501
548
  if member in data:
502
- if not schema.is_valid(data[member], cast=cast):
549
+ if not schema.is_valid(data[member], cast=cast, **kwargs):
503
550
  return False
504
551
  members[member] = data[member].lazy()
505
552
 
@@ -510,9 +557,12 @@ class Collection(BaseCollection, ABC):
510
557
  keep = [filter.logic(result_cls).select(primary_key) for filter in filters]
511
558
  joined = _join_all(*keep, on=primary_key, how="inner")
512
559
  removed_rows = pl.collect_all(
513
- data[member].lazy().join(joined, on=primary_key, how="anti")
514
- for member in cls.members()
515
- if member in data
560
+ (
561
+ data[member].lazy().join(joined, on=primary_key, how="anti")
562
+ for member in cls.members()
563
+ if member in data
564
+ ),
565
+ **kwargs,
516
566
  )
517
567
  return all(df.is_empty() for df in removed_rows)
518
568
 
@@ -522,7 +572,14 @@ class Collection(BaseCollection, ABC):
522
572
 
523
573
  @classmethod
524
574
  def filter(
525
- cls, data: Mapping[str, FrameType], /, *, cast: bool = False, eager: bool = True
575
+ cls,
576
+ data: Mapping[str, FrameType],
577
+ /,
578
+ *,
579
+ cast: bool = False,
580
+ eager: bool = True,
581
+ skip_member_validation: bool = False,
582
+ **kwargs: Any,
526
583
  ) -> CollectionFilterResult[Self]:
527
584
  """Filter the members data frame by their schemas and the collection's filters.
528
585
 
@@ -538,6 +595,13 @@ class Collection(BaseCollection, ABC):
538
595
  eager: Whether the filter operation should be performed eagerly.
539
596
  Note that until https://github.com/pola-rs/polars/pull/24129 is
540
597
  released, eagerly filtering can provide significant speedups.
598
+ skip_member_validation: Whether to skip filtering individual members and only
599
+ apply the collection filters. **Use this option with caution** as it
600
+ requires the caller to ensure that the individual members have been
601
+ validated. This option is particularly useful in performance-critical
602
+ scenarios where the members are known to already be valid.
603
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
604
+ :meth:`polars.LazyFrame.collect` when `eager=True`.
541
605
 
542
606
  Returns:
543
607
  A named tuple with fields `result` and `failure`. The `result` field
@@ -579,10 +643,20 @@ class Collection(BaseCollection, ABC):
579
643
  if member.is_optional and member_name not in data:
580
644
  continue
581
645
 
582
- member_result, failures[member_name] = member.schema.filter(
583
- data[member_name].lazy(), cast=cast, eager=eager
584
- )
585
- results[member_name] = member_result.lazy()
646
+ if skip_member_validation:
647
+ results[member_name] = (
648
+ member.schema.cast(data[member_name].lazy())
649
+ if cast
650
+ else data[member_name].lazy()
651
+ )
652
+ failures[member_name] = FailureInfo._create_empty(
653
+ member.schema, with_casting_rules=cast
654
+ )
655
+ else:
656
+ member_result, failures[member_name] = member.schema.filter(
657
+ data[member_name].lazy(), cast=cast, eager=eager, **kwargs
658
+ )
659
+ results[member_name] = member_result.lazy()
586
660
 
587
661
  # Once we've done that, we can apply the filters on this collection. To this end,
588
662
  # we iterate over all filters and store the filter results.
@@ -592,28 +666,25 @@ class Collection(BaseCollection, ABC):
592
666
  result_cls = cls._init(results)
593
667
  primary_key = cls.common_primary_key()
594
668
 
595
- keep: dict[str, pl.LazyFrame] = {}
596
- for name, filter in filters.items():
597
- keep[name] = (
598
- filter.logic(result_cls)
599
- .select(primary_key)
600
- .pipe(collect_if, eager)
601
- .lazy()
602
- )
669
+ keep = {
670
+ name: filter.logic(result_cls).select(primary_key)
671
+ for name, filter in filters.items()
672
+ }
673
+ keep = collect_all_if(keep, eager, **kwargs)
603
674
 
604
- drop: dict[str, pl.LazyFrame] = {}
605
- for failure_propagating_member in failure_propagating_members:
606
- annotation_column = f"{failure_propagating_member}|failure_propagation"
607
- drop[annotation_column] = (
675
+ drop: dict[str, pl.LazyFrame] = {
676
+ f"{failure_propagating_member}|failure_propagation": (
608
677
  failures[failure_propagating_member]
609
678
  ._lf.select(primary_key)
610
679
  .unique()
611
- .pipe(collect_if, eager)
612
- .lazy()
613
680
  )
681
+ for failure_propagating_member in failure_propagating_members
682
+ }
683
+ drop = collect_all_if(drop, eager, **kwargs)
614
684
 
615
685
  # Now we can iterate over the results and left-join onto each individual
616
- # filter to obtain independent boolean indicators of whether to keep the row
686
+ # filter to obtain independent boolean indicators of whether to keep the row.
687
+ lfs_with_eval: dict[str, pl.LazyFrame] = {}
617
688
  for member_name, filtered in results.items():
618
689
  member_info = cls.members()[member_name]
619
690
  if member_info.ignored_in_filters:
@@ -635,7 +706,11 @@ class Collection(BaseCollection, ABC):
635
706
  maintain_order="left",
636
707
  ).with_columns(pl.col(name).fill_null(True))
637
708
 
638
- lf_with_eval = lf_with_eval.pipe(collect_if, eager).lazy()
709
+ lfs_with_eval[member_name] = lf_with_eval
710
+
711
+ lfs_with_eval = collect_all_if(lfs_with_eval, eager, **kwargs)
712
+ for member_name, lf_with_eval in lfs_with_eval.items():
713
+ member_info = cls.members()[member_name]
639
714
 
640
715
  # Filtering `lf_with_eval` by the rows for which all joins
641
716
  # "succeeded", we can identify the rows that pass all the filters. We
@@ -700,7 +775,7 @@ class Collection(BaseCollection, ABC):
700
775
 
701
776
  result = CollectionFilterResult(cls._init(results), failures)
702
777
  if eager:
703
- return result.collect_all()
778
+ return result.collect_all(**kwargs)
704
779
  return result
705
780
 
706
781
  def join(
@@ -795,22 +870,52 @@ class Collection(BaseCollection, ABC):
795
870
 
796
871
  # ---------------------------------- COLLECTION ---------------------------------- #
797
872
 
798
- def collect_all(self) -> Self:
873
+ def collect_all(self, **kwargs: Any) -> Self:
799
874
  """Collect all members of the collection.
800
875
 
801
876
  This method collects all members in parallel for maximum efficiency. It is
802
877
  particularly useful when :meth:`filter` is called with lazy frame inputs.
803
878
 
879
+ Args:
880
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all`.
881
+
804
882
  Returns:
805
883
  The same collection with all members collected once. Members annotated
806
884
  with :class:`~dataframely.DataFrame` are returned as DataFrames, while
807
885
  members annotated with :class:`~dataframely.LazyFrame` are returned as
808
- "shallow-lazy" frames (obtained by calling ``.collect().lazy()``).
886
+ "shallow-lazy" frames (obtained by calling `.collect().lazy()`).
809
887
  """
810
888
  lazy_dict = self.to_dict()
811
- dfs = pl.collect_all(lazy_dict.values())
889
+ dfs = pl.collect_all(lazy_dict.values(), **kwargs)
812
890
  return self._init(dict(zip(lazy_dict, dfs)))
813
891
 
892
+ def pipe(
893
+ self,
894
+ function: Callable[Concatenate[Self, P], T],
895
+ *args: P.args,
896
+ **kwargs: P.kwargs,
897
+ ) -> T:
898
+ """Apply a function to this collection.
899
+
900
+ This method allows chaining operations on a collection in a fluent style,
901
+ analogously to :meth:`polars.LazyFrame.pipe`.
902
+
903
+ Args:
904
+ function: The callable to apply. It receives this collection as its first
905
+ argument, followed by any additional ``args`` and ``kwargs``.
906
+ args: Additional positional arguments to pass to ``function``.
907
+ kwargs: Additional keyword arguments to pass to ``function``.
908
+
909
+ Returns:
910
+ The return value of ``function`` when called as described.
911
+
912
+ Example:
913
+ >>> def add_prefix(collection: MyCollection, prefix: str) -> MyCollection:
914
+ ... ...
915
+ >>> result = my_collection.pipe(add_prefix, prefix="foo")
916
+ """
917
+ return function(self, *args, **kwargs)
918
+
814
919
  # --------------------------------- SERIALIZATION -------------------------------- #
815
920
 
816
921
  @classmethod
@@ -106,6 +106,17 @@ class FailureInfo(Generic[S]):
106
106
  self._rule_columns = rule_columns
107
107
  self.schema = schema
108
108
 
109
+ @classmethod
110
+ def _create_empty(cls, schema: type[S], with_casting_rules: bool) -> FailureInfo[S]:
111
+ rules = schema._validation_rules(with_cast=with_casting_rules)
112
+ lf = pl.LazyFrame(
113
+ schema={
114
+ **schema.to_polars_schema(), # type: ignore
115
+ **{rule: pl.Boolean for rule in rules},
116
+ }
117
+ )
118
+ return cls(lf=lf, rule_columns=list(rules.keys()), schema=schema)
119
+
109
120
  @cached_property
110
121
  def _df(self) -> pl.DataFrame:
111
122
  return self._lf.collect()
@@ -84,9 +84,9 @@ def require_relationship_one_to_at_least_one(
84
84
  columns, filtered to ensure a 1:{1,N} relationship.
85
85
  """
86
86
  if drop_duplicates:
87
- return lhs.unique(on, keep="none").join(rhs.unique(on), on=on)
87
+ return lhs.unique(on, keep="none").join(rhs, on=on, how="semi")
88
88
 
89
- return lhs.join(rhs.unique(on), on=on)
89
+ return lhs.join(rhs, on=on, how="semi")
90
90
 
91
91
 
92
92
  # ------------------------------------------------------------------------------------ #
@@ -501,6 +501,7 @@ class Schema(BaseSchema, ABC):
501
501
  *,
502
502
  cast: bool = False,
503
503
  eager: Literal[True] = True,
504
+ **kwargs: Any,
504
505
  ) -> DataFrame[Self]: ...
505
506
 
506
507
  @overload
@@ -512,6 +513,7 @@ class Schema(BaseSchema, ABC):
512
513
  *,
513
514
  cast: bool = False,
514
515
  eager: Literal[False],
516
+ **kwargs: Any,
515
517
  ) -> LazyFrame[Self]: ...
516
518
 
517
519
  @overload
@@ -523,6 +525,7 @@ class Schema(BaseSchema, ABC):
523
525
  *,
524
526
  cast: bool = False,
525
527
  eager: bool,
528
+ **kwargs: Any,
526
529
  ) -> DataFrame[Self] | LazyFrame[Self]: ...
527
530
 
528
531
  @classmethod
@@ -533,6 +536,7 @@ class Schema(BaseSchema, ABC):
533
536
  *,
534
537
  cast: bool = False,
535
538
  eager: bool = True,
539
+ **kwargs: Any,
536
540
  ) -> DataFrame[Self] | LazyFrame[Self]:
537
541
  """Validate that a data frame satisfies the schema.
538
542
 
@@ -554,6 +558,8 @@ class Schema(BaseSchema, ABC):
554
558
  not surface *all* validation issues as the validation is aborted
555
559
  once the first failure is encountered. Likewise, the reported
556
560
  validation failure can be non-deterministic.
561
+ kwargs: Keyword arguments passed directly to :meth:`polars.LazyFrame.collect`
562
+ when `eager=True`.
557
563
 
558
564
  Returns:
559
565
  The input eager or lazy frame, wrapped in a generic version of the
@@ -574,7 +580,7 @@ class Schema(BaseSchema, ABC):
574
580
  `eager=False`.
575
581
  """
576
582
  if eager:
577
- out, failure = cls.filter(df, cast=cast, eager=True)
583
+ out, failure = cls.filter(df, cast=cast, eager=True, **kwargs)
578
584
  if len(failure) > 0:
579
585
  counts = failure.counts()
580
586
  raise ValidationError(
@@ -608,7 +614,7 @@ class Schema(BaseSchema, ABC):
608
614
 
609
615
  @classmethod
610
616
  def is_valid(
611
- cls, df: pl.DataFrame | pl.LazyFrame, /, *, cast: bool = False
617
+ cls, df: pl.DataFrame | pl.LazyFrame, /, *, cast: bool = False, **kwargs: Any
612
618
  ) -> bool:
613
619
  """Check whether a data frame satisfies the schema.
614
620
 
@@ -627,6 +633,7 @@ class Schema(BaseSchema, ABC):
627
633
  cast to the schema's defined data type before running validation. If set
628
634
  to `False`, a wrong data type will result in a return value of
629
635
  `False`.
636
+ kwargs: Keyword arguments passed directly to :meth:`polars.LazyFrame.collect`.
630
637
 
631
638
  Returns:
632
639
  Whether the provided dataframe can be validated with this schema.
@@ -646,12 +653,12 @@ class Schema(BaseSchema, ABC):
646
653
  return (
647
654
  lf.pipe(with_evaluation_rules, rules)
648
655
  .select(all_rules(rules.keys()))
649
- .collect()
656
+ .collect(**kwargs)
650
657
  .item()
651
658
  )
652
659
  # NOTE: We cannot simply return `True` here as, otherwise, we wouldn't
653
660
  # validate the schema.
654
- return lf.select(pl.lit(True)).collect().item()
661
+ return lf.select(pl.lit(True)).collect(**kwargs).item()
655
662
  except SchemaError:
656
663
  # If we encounter a schema error, we gracefully handle this as 'invalid'
657
664
  return False
@@ -699,6 +706,7 @@ class Schema(BaseSchema, ABC):
699
706
  *,
700
707
  cast: bool = False,
701
708
  eager: bool = True,
709
+ **kwargs: Any,
702
710
  ) -> FilterResult[Self] | LazyFilterResult[Self]:
703
711
  """Filter the data frame by the rules of this schema, returning `(valid,
704
712
  failures)`.
@@ -709,16 +717,16 @@ class Schema(BaseSchema, ABC):
709
717
  succeeds.
710
718
 
711
719
  Args:
712
- df: The data frame to filter for valid rows.
713
- The data frame is collected within this method, regardless of whether
714
- a :class:`~polars.DataFrame` or :class:`~polars.LazyFrame` is passed.
715
- cast:
716
- Whether columns with a wrong data type in the input data frame are
720
+ df: The data frame to filter for valid rows. The data frame is collected
721
+ within this method, regardless of whether a :class:`~polars.DataFrame`
722
+ or :class:`~polars.LazyFrame` is passed.
723
+ cast: Whether columns with a wrong data type in the input data frame are
717
724
  cast to the schema's defined data type if possible. Rows for which the
718
725
  cast fails for any column are filtered out.
719
726
  eager: Whether the filter operation should be performed eagerly. If `False`, the
720
- returned lazy frame will
721
- fail to collect if the validation does not pass.
727
+ returned lazy frame will fail to collect if the validation does not pass.
728
+ kwargs: Keyword arguments passed directly to :meth:`polars.LazyFrame.collect`
729
+ when `eager=True`.
722
730
 
723
731
  Returns:
724
732
  A tuple of the validated rows in the input data frame (potentially
@@ -754,8 +762,8 @@ class Schema(BaseSchema, ABC):
754
762
  match_to_schema, cls, casting=("lenient" if cast else "none")
755
763
  )
756
764
  if rules := cls._validation_rules(with_cast=cast):
757
- evaluated = (
758
- lf.pipe(cls._with_evaluated_rules, rules).pipe(collect_if, eager).lazy()
765
+ evaluated = lf.pipe(cls._with_evaluated_rules, rules).pipe(
766
+ collect_if, eager, **kwargs
759
767
  )
760
768
  filtered = evaluated.filter(pl.col(_COLUMN_VALID)).select(
761
769
  cls.column_names()
@@ -8,4 +8,5 @@ Operations
8
8
 
9
9
  Collection.collect_all
10
10
  Collection.join
11
+ Collection.pipe
11
12
  concat_collection_members
@@ -27,7 +27,7 @@ description = "A declarative, polars-native data frame validation library"
27
27
  name = "dataframely"
28
28
  readme = "README.md"
29
29
  requires-python = ">=3.10"
30
- version = "2.11.0"
30
+ version = "2.13.0"
31
31
 
32
32
  [project.optional-dependencies]
33
33
  deltalake = ["deltalake"]
@@ -15,13 +15,13 @@ def partitioned_dataset(dataset: pl.DataFrame) -> dict[str, pl.DataFrame]:
15
15
  "elevation",
16
16
  "aspect",
17
17
  "slope",
18
- idx=pl.int_range(pl.len(), dtype=pl.UInt32),
18
+ idx=pl.int_range(pl.len(), dtype=pl.UInt32).shuffle(),
19
19
  ),
20
20
  "second": dataset.select(
21
21
  "horizontal_distance_to_hydrology",
22
22
  "vertical_distance_to_hydrology",
23
23
  "horizontal_distance_to_roadways",
24
- idx=pl.int_range(pl.len(), dtype=pl.UInt32),
24
+ idx=pl.int_range(pl.len(), dtype=pl.UInt32).shuffle(),
25
25
  ),
26
26
  }
27
27
 
@@ -95,18 +95,24 @@ class MultiFilterCollection(dy.Collection):
95
95
 
96
96
 
97
97
  @pytest.mark.benchmark(group="collection-filter-multi")
98
+ @pytest.mark.parametrize("engine", ["in-memory", "streaming"])
98
99
  def test_multi_filter_validate(
99
- benchmark: BenchmarkFixture, partitioned_dataset: dict[str, pl.DataFrame]
100
+ benchmark: BenchmarkFixture,
101
+ partitioned_dataset: dict[str, pl.DataFrame],
102
+ engine: str,
100
103
  ) -> None:
101
- benchmark(MultiFilterCollection.validate, partitioned_dataset)
104
+ benchmark(MultiFilterCollection.validate, partitioned_dataset, engine=engine)
102
105
 
103
106
 
104
107
  @pytest.mark.benchmark(group="collection-filter-multi")
108
+ @pytest.mark.parametrize("engine", ["in-memory", "streaming"])
105
109
  def test_multi_filter_filter(
106
- benchmark: BenchmarkFixture, partitioned_dataset: dict[str, pl.DataFrame]
110
+ benchmark: BenchmarkFixture,
111
+ partitioned_dataset: dict[str, pl.DataFrame],
112
+ engine: str,
107
113
  ) -> None:
108
114
  def benchmark_fn() -> None:
109
- _, failure = MultiFilterCollection.filter(partitioned_dataset)
115
+ _, failure = MultiFilterCollection.filter(partitioned_dataset, engine=engine)
110
116
  _ = [len(f) for f in failure.values()]
111
117
 
112
118
  benchmark(benchmark_fn)
@@ -0,0 +1,55 @@
1
+ # Copyright (c) QuantCo 2025-2026
2
+ # SPDX-License-Identifier: BSD-3-Clause
3
+
4
+ import dataframely as dy
5
+
6
+
7
+ class SchemaOne(dy.Schema):
8
+ id = dy.Int64(primary_key=True)
9
+ name = dy.String(nullable=False)
10
+
11
+
12
+ class SchemaTwo(dy.Schema):
13
+ id = dy.Int64(primary_key=True)
14
+ name = dy.String(nullable=False)
15
+
16
+
17
+ class MyCollection(dy.Collection):
18
+ member_one: dy.LazyFrame[SchemaOne]
19
+ member_two: dy.LazyFrame[SchemaTwo]
20
+
21
+
22
+ def test_pipe_passes_self() -> None:
23
+ # Arrange
24
+ collection = MyCollection.sample(overrides=[{"id": 1}, {"id": 2}])
25
+
26
+ # Act
27
+ result = collection.pipe(lambda c: c)
28
+
29
+ # Assert
30
+ assert result is collection
31
+
32
+
33
+ def test_pipe_forwards_args_and_kwargs() -> None:
34
+ # Arrange
35
+ collection = MyCollection.sample(overrides=[{"id": 1}, {"id": 2}])
36
+
37
+ def combine(c: MyCollection, prefix: str, *, suffix: str) -> str:
38
+ return f"{prefix}{type(c).__name__}{suffix}"
39
+
40
+ # Act
41
+ result = collection.pipe(combine, "pre-", suffix="-post")
42
+
43
+ # Assert
44
+ assert result == "pre-MyCollection-post"
45
+
46
+
47
+ def test_pipe_returns_arbitrary_type() -> None:
48
+ # Arrange
49
+ collection = MyCollection.sample(overrides=[{"id": 1}, {"id": 2}, {"id": 3}])
50
+
51
+ # Act
52
+ result = collection.pipe(lambda c: c.member_one.collect().height)
53
+
54
+ # Assert
55
+ assert result == 3