dataframely 2.2.0__tar.gz → 2.3.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 (216) hide show
  1. {dataframely-2.2.0 → dataframely-2.3.0}/PKG-INFO +1 -1
  2. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/collection/_base.py +17 -0
  3. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/collection/collection.py +25 -5
  4. {dataframely-2.2.0 → dataframely-2.3.0}/pyproject.toml +1 -1
  5. dataframely-2.3.0/tests/collection/test_propagate_row_failures.py +126 -0
  6. {dataframely-2.2.0 → dataframely-2.3.0}/.copier-answers.yml +0 -0
  7. {dataframely-2.2.0 → dataframely-2.3.0}/.envrc +0 -0
  8. {dataframely-2.2.0 → dataframely-2.3.0}/.gitattributes +0 -0
  9. {dataframely-2.2.0 → dataframely-2.3.0}/.github/CODEOWNERS +0 -0
  10. {dataframely-2.2.0 → dataframely-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  11. {dataframely-2.2.0 → dataframely-2.3.0}/.github/copilot-instructions.md +0 -0
  12. {dataframely-2.2.0 → dataframely-2.3.0}/.github/dependabot.yml +0 -0
  13. {dataframely-2.2.0 → dataframely-2.3.0}/.github/instructions/tests.instructions.md +0 -0
  14. {dataframely-2.2.0 → dataframely-2.3.0}/.github/release-drafter.yml +0 -0
  15. {dataframely-2.2.0 → dataframely-2.3.0}/.github/workflows/build.yml +0 -0
  16. {dataframely-2.2.0 → dataframely-2.3.0}/.github/workflows/chore.yml +0 -0
  17. {dataframely-2.2.0 → dataframely-2.3.0}/.github/workflows/ci.yml +0 -0
  18. {dataframely-2.2.0 → dataframely-2.3.0}/.github/workflows/copilot-setup-steps.yml +0 -0
  19. {dataframely-2.2.0 → dataframely-2.3.0}/.github/workflows/nightly.yml +0 -0
  20. {dataframely-2.2.0 → dataframely-2.3.0}/.github/workflows/scorecard.yml +0 -0
  21. {dataframely-2.2.0 → dataframely-2.3.0}/.gitignore +0 -0
  22. {dataframely-2.2.0 → dataframely-2.3.0}/.pre-commit-config.yaml +0 -0
  23. {dataframely-2.2.0 → dataframely-2.3.0}/.prettierignore +0 -0
  24. {dataframely-2.2.0 → dataframely-2.3.0}/.prettierrc +0 -0
  25. {dataframely-2.2.0 → dataframely-2.3.0}/.readthedocs.yml +0 -0
  26. {dataframely-2.2.0 → dataframely-2.3.0}/Cargo.lock +0 -0
  27. {dataframely-2.2.0 → dataframely-2.3.0}/Cargo.toml +0 -0
  28. {dataframely-2.2.0 → dataframely-2.3.0}/LICENSE +0 -0
  29. {dataframely-2.2.0 → dataframely-2.3.0}/README.md +0 -0
  30. {dataframely-2.2.0 → dataframely-2.3.0}/SECURITY.md +0 -0
  31. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/__init__.py +0 -0
  32. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_base_schema.py +0 -0
  33. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_compat.py +0 -0
  34. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_deprecation.py +0 -0
  35. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_filter.py +0 -0
  36. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_match_to_schema.py +0 -0
  37. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_native.pyi +0 -0
  38. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_plugin.py +0 -0
  39. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_polars.py +0 -0
  40. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_pydantic.py +0 -0
  41. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_rule.py +0 -0
  42. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_serialization.py +0 -0
  43. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_storage/__init__.py +0 -0
  44. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_storage/_base.py +0 -0
  45. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_storage/_exc.py +0 -0
  46. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_storage/constants.py +0 -0
  47. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_storage/delta.py +0 -0
  48. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_storage/parquet.py +0 -0
  49. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/_typing.py +0 -0
  50. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/collection/__init__.py +0 -0
  51. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/collection/filter_result.py +0 -0
  52. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/__init__.py +0 -0
  53. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/_base.py +0 -0
  54. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/_mixins.py +0 -0
  55. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/_registry.py +0 -0
  56. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/_utils.py +0 -0
  57. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/any.py +0 -0
  58. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/array.py +0 -0
  59. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/binary.py +0 -0
  60. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/bool.py +0 -0
  61. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/categorical.py +0 -0
  62. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/datetime.py +0 -0
  63. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/decimal.py +0 -0
  64. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/enum.py +0 -0
  65. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/float.py +0 -0
  66. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/integer.py +0 -0
  67. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/list.py +0 -0
  68. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/object.py +0 -0
  69. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/string.py +0 -0
  70. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/columns/struct.py +0 -0
  71. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/config.py +0 -0
  72. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/exc.py +0 -0
  73. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/filter_result.py +0 -0
  74. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/functional.py +0 -0
  75. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/py.typed +0 -0
  76. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/random.py +0 -0
  77. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/schema.py +0 -0
  78. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/testing/__init__.py +0 -0
  79. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/testing/const.py +0 -0
  80. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/testing/factory.py +0 -0
  81. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/testing/mask.py +0 -0
  82. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/testing/rules.py +0 -0
  83. {dataframely-2.2.0 → dataframely-2.3.0}/dataframely/testing/storage.py +0 -0
  84. {dataframely-2.2.0 → dataframely-2.3.0}/docker-compose.yml +0 -0
  85. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_static/custom.css +0 -0
  86. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_static/favicon.ico +0 -0
  87. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_templates/autosummary/class.rst +0 -0
  88. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_templates/autosummary/method.rst +0 -0
  89. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_templates/classes/column.rst +0 -0
  90. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_templates/classes/error.rst +0 -0
  91. {dataframely-2.2.0 → dataframely-2.3.0}/docs/_templates/classes/filter_result.rst +0 -0
  92. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/collection/generation.rst +0 -0
  93. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/collection/index.rst +0 -0
  94. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/collection/io.rst +0 -0
  95. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/collection/metadata.rst +0 -0
  96. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/collection/operations.rst +0 -0
  97. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/collection/validation.rst +0 -0
  98. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/columns/index.rst +0 -0
  99. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/errors/index.rst +0 -0
  100. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/filter_result/failure_info.rst +0 -0
  101. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/filter_result/index.rst +0 -0
  102. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/index.rst +0 -0
  103. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/misc/index.rst +0 -0
  104. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/schema/conversion.rst +0 -0
  105. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/schema/generation.rst +0 -0
  106. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/schema/index.rst +0 -0
  107. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/schema/io.rst +0 -0
  108. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/schema/metadata.rst +0 -0
  109. {dataframely-2.2.0 → dataframely-2.3.0}/docs/api/schema/validation.rst +0 -0
  110. {dataframely-2.2.0 → dataframely-2.3.0}/docs/conf.py +0 -0
  111. {dataframely-2.2.0 → dataframely-2.3.0}/docs/css/custom.css +0 -0
  112. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/development.md +0 -0
  113. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/examples/index.md +0 -0
  114. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/examples/real-world.ipynb +0 -0
  115. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/faq.md +0 -0
  116. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/column-metadata.md +0 -0
  117. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/data-generation.md +0 -0
  118. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/index.md +0 -0
  119. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/lazy-validation.md +0 -0
  120. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/primary-keys.md +0 -0
  121. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/serialization.md +0 -0
  122. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/features/sql-generation.md +0 -0
  123. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/index.md +0 -0
  124. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/migration/index.md +0 -0
  125. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/migration/v1-v2.md +0 -0
  126. {dataframely-2.2.0 → dataframely-2.3.0}/docs/guides/quickstart.md +0 -0
  127. {dataframely-2.2.0 → dataframely-2.3.0}/docs/index.md +0 -0
  128. {dataframely-2.2.0 → dataframely-2.3.0}/pixi.lock +0 -0
  129. {dataframely-2.2.0 → dataframely-2.3.0}/pixi.toml +0 -0
  130. {dataframely-2.2.0 → dataframely-2.3.0}/rust-toolchain.toml +0 -0
  131. {dataframely-2.2.0 → dataframely-2.3.0}/src/lib.rs +0 -0
  132. {dataframely-2.2.0 → dataframely-2.3.0}/src/polars_plugin/mod.rs +0 -0
  133. {dataframely-2.2.0 → dataframely-2.3.0}/src/polars_plugin/rule_failure.rs +0 -0
  134. {dataframely-2.2.0 → dataframely-2.3.0}/src/polars_plugin/utils.rs +0 -0
  135. {dataframely-2.2.0 → dataframely-2.3.0}/src/polars_plugin/validation_error.rs +0 -0
  136. {dataframely-2.2.0 → dataframely-2.3.0}/src/regex/errdefs.rs +0 -0
  137. {dataframely-2.2.0 → dataframely-2.3.0}/src/regex/mod.rs +0 -0
  138. {dataframely-2.2.0 → dataframely-2.3.0}/src/regex/repr.rs +0 -0
  139. {dataframely-2.2.0 → dataframely-2.3.0}/tests/benches/conftest.py +0 -0
  140. {dataframely-2.2.0 → dataframely-2.3.0}/tests/benches/test_collection.py +0 -0
  141. {dataframely-2.2.0 → dataframely-2.3.0}/tests/benches/test_failure.py +0 -0
  142. {dataframely-2.2.0 → dataframely-2.3.0}/tests/benches/test_schema.py +0 -0
  143. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_base.py +0 -0
  144. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_cast.py +0 -0
  145. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_collection_future_annotations.py +0 -0
  146. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_create_empty.py +0 -0
  147. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_filter_one_to_n.py +0 -0
  148. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_filter_validate.py +0 -0
  149. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_ignore_in_filter.py +0 -0
  150. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_implementation.py +0 -0
  151. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_join.py +0 -0
  152. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_matches.py +0 -0
  153. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_optional_members.py +0 -0
  154. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_repr.py +0 -0
  155. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_sample.py +0 -0
  156. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_serialization.py +0 -0
  157. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_storage.py +0 -0
  158. {dataframely-2.2.0 → dataframely-2.3.0}/tests/collection/test_validate_input.py +0 -0
  159. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/__init__.py +0 -0
  160. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_any.py +0 -0
  161. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_array.py +0 -0
  162. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_binary.py +0 -0
  163. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_datetime.py +0 -0
  164. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_decimal.py +0 -0
  165. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_enum.py +0 -0
  166. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_float.py +0 -0
  167. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_integer.py +0 -0
  168. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_list.py +0 -0
  169. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_object.py +0 -0
  170. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_string.py +0 -0
  171. {dataframely-2.2.0 → dataframely-2.3.0}/tests/column_types/test_struct.py +0 -0
  172. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/__init__.py +0 -0
  173. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_alias.py +0 -0
  174. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_base.py +0 -0
  175. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_check.py +0 -0
  176. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_default_dtypes.py +0 -0
  177. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_matches.py +0 -0
  178. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_metadata.py +0 -0
  179. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_polars_schema.py +0 -0
  180. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_pyarrow.py +0 -0
  181. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_rules.py +0 -0
  182. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_sample.py +0 -0
  183. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_sqlalchemy_columns.py +0 -0
  184. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_str.py +0 -0
  185. {dataframely-2.2.0 → dataframely-2.3.0}/tests/columns/test_utils.py +0 -0
  186. {dataframely-2.2.0 → dataframely-2.3.0}/tests/conftest.py +0 -0
  187. {dataframely-2.2.0 → dataframely-2.3.0}/tests/core_validation/__init__.py +0 -0
  188. {dataframely-2.2.0 → dataframely-2.3.0}/tests/core_validation/test_match_to_schema.py +0 -0
  189. {dataframely-2.2.0 → dataframely-2.3.0}/tests/core_validation/test_rule_evaluation.py +0 -0
  190. {dataframely-2.2.0 → dataframely-2.3.0}/tests/failure_info/test_storage.py +0 -0
  191. {dataframely-2.2.0 → dataframely-2.3.0}/tests/functional/test_concat.py +0 -0
  192. {dataframely-2.2.0 → dataframely-2.3.0}/tests/functional/test_relationships.py +0 -0
  193. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_base.py +0 -0
  194. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_cast.py +0 -0
  195. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_create_empty.py +0 -0
  196. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_create_empty_if_none.py +0 -0
  197. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_filter.py +0 -0
  198. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_inheritance.py +0 -0
  199. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_matches.py +0 -0
  200. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_read_write_parquet.py +0 -0
  201. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_repr.py +0 -0
  202. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_rule_implementation.py +0 -0
  203. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_sample.py +0 -0
  204. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_serialization.py +0 -0
  205. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_storage.py +0 -0
  206. {dataframely-2.2.0 → dataframely-2.3.0}/tests/schema/test_validate.py +0 -0
  207. {dataframely-2.2.0 → dataframely-2.3.0}/tests/storage/test_delta.py +0 -0
  208. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_compat.py +0 -0
  209. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_config.py +0 -0
  210. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_deprecation.py +0 -0
  211. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_factory.py +0 -0
  212. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_native_regex.py +0 -0
  213. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_pydantic.py +0 -0
  214. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_random.py +0 -0
  215. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_serialization.py +0 -0
  216. {dataframely-2.2.0 → dataframely-2.3.0}/tests/test_typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframely
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.10
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -65,6 +65,11 @@ class CollectionMember:
65
65
  #: the collection's common primary key. Two members that share common column names
66
66
  #: may not both be inlined for sampling.
67
67
  inline_for_sampling: bool = False
68
+ #: Whether individual row failures in this member should be propagated to the
69
+ #: collection, i.e., cause the common primary key of the failures to be filtered
70
+ #: out from the entire collection. This setting is ignored if `ignored_in_filters`
71
+ #: is `True`.
72
+ propagate_row_failures: bool = False
68
73
 
69
74
 
70
75
  # --------------------------------------- UTILS -------------------------------------- #
@@ -250,6 +255,7 @@ class CollectionMeta(ABCMeta):
250
255
  is_optional=True,
251
256
  ignored_in_filters=collection_member.ignored_in_filters,
252
257
  inline_for_sampling=collection_member.inline_for_sampling,
258
+ propagate_row_failures=collection_member.propagate_row_failures,
253
259
  )
254
260
  elif issubclass(origin, TypedLazyFrame):
255
261
  # Happy path: required member
@@ -258,6 +264,7 @@ class CollectionMeta(ABCMeta):
258
264
  is_optional=False,
259
265
  ignored_in_filters=collection_member.ignored_in_filters,
260
266
  inline_for_sampling=collection_member.inline_for_sampling,
267
+ propagate_row_failures=collection_member.propagate_row_failures,
261
268
  )
262
269
  else:
263
270
  # Some other unknown annotation
@@ -333,6 +340,16 @@ class BaseCollection(metaclass=CollectionMeta):
333
340
  if not member.ignored_in_filters
334
341
  }
335
342
 
343
+ @classmethod
344
+ def _failure_propagating_members(cls) -> set[str]:
345
+ """The names of all members of the collection that propagate individual row
346
+ failures to the collection."""
347
+ return {
348
+ name
349
+ for name, member in cls.members().items()
350
+ if member.propagate_row_failures
351
+ }
352
+
336
353
  @classmethod
337
354
  def common_primary_key(cls) -> list[str]:
338
355
  """The primary keys shared by non ignored members of the collection."""
@@ -573,7 +573,8 @@ class Collection(BaseCollection, ABC):
573
573
  # Once we've done that, we can apply the filters on this collection. To this end,
574
574
  # we iterate over all filters and store the filter results.
575
575
  filters = cls._filters()
576
- if len(filters) > 0:
576
+ failure_propagating_members = cls._failure_propagating_members()
577
+ if len(filters) > 0 or len(failure_propagating_members) > 0:
577
578
  result_cls = cls._init(results)
578
579
  primary_key = cls.common_primary_key()
579
580
 
@@ -586,6 +587,17 @@ class Collection(BaseCollection, ABC):
586
587
  .lazy()
587
588
  )
588
589
 
590
+ drop: dict[str, pl.LazyFrame] = {}
591
+ for failure_propagating_member in failure_propagating_members:
592
+ annotation_column = f"{failure_propagating_member}|failure_propagation"
593
+ drop[annotation_column] = (
594
+ failures[failure_propagating_member]
595
+ ._lf.select(primary_key)
596
+ .unique()
597
+ .pipe(collect_if, eager)
598
+ .lazy()
599
+ )
600
+
589
601
  # Now we can iterate over the results and left-join onto each individual
590
602
  # filter to obtain independent boolean indicators of whether to keep the row
591
603
  for member_name, filtered in results.items():
@@ -601,15 +613,23 @@ class Collection(BaseCollection, ABC):
601
613
  how="left",
602
614
  maintain_order="left",
603
615
  ).with_columns(pl.col(name).fill_null(False))
616
+ for name, filter_drop in drop.items():
617
+ lf_with_eval = lf_with_eval.join(
618
+ filter_drop.with_columns(pl.lit(False).alias(name)),
619
+ on=primary_key,
620
+ how="left",
621
+ maintain_order="left",
622
+ ).with_columns(pl.col(name).fill_null(True))
604
623
 
605
624
  lf_with_eval = lf_with_eval.pipe(collect_if, eager).lazy()
606
625
 
607
626
  # Filtering `lf_with_eval` by the rows for which all joins
608
627
  # "succeeded", we can identify the rows that pass all the filters. We
609
628
  # keep these rows for the result.
629
+ all_filter_columns = list(keep.keys()) + list(drop.keys())
610
630
  results[member_name] = lf_with_eval.filter(
611
- pl.all_horizontal(keep.keys())
612
- ).drop(keep.keys())
631
+ pl.all_horizontal(all_filter_columns)
632
+ ).drop(all_filter_columns)
613
633
 
614
634
  # Filtering `lf_with_eval` with the inverse condition, we find all
615
635
  # the problematic rows. We can build a single failure info object by
@@ -623,7 +643,7 @@ class Collection(BaseCollection, ABC):
623
643
  #
624
644
  failure = failures[member_name]
625
645
  filtered_failure = lf_with_eval.filter(
626
- ~pl.all_horizontal(keep.keys())
646
+ ~pl.all_horizontal(all_filter_columns)
627
647
  ).lazy()
628
648
 
629
649
  # If we cast previously, `failure` and `filtered_failure` have different
@@ -660,7 +680,7 @@ class Collection(BaseCollection, ABC):
660
680
 
661
681
  failures[member_name] = FailureInfo(
662
682
  lf=failure_lf,
663
- rule_columns=failure._rule_columns + list(keep.keys()),
683
+ rule_columns=failure._rule_columns + all_filter_columns,
664
684
  schema=failure.schema,
665
685
  )
666
686
 
@@ -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.2.0"
30
+ version = "2.3.0"
31
31
 
32
32
  [project.optional-dependencies]
33
33
  deltalake = ["deltalake"]
@@ -0,0 +1,126 @@
1
+ # Copyright (c) QuantCo 2025-2025
2
+ # SPDX-License-Identifier: BSD-3-Clause
3
+
4
+ from typing import Annotated
5
+
6
+ import polars as pl
7
+ import pytest
8
+
9
+ import dataframely as dy
10
+
11
+
12
+ class MyTestSchema(dy.Schema):
13
+ shared = dy.UInt8(primary_key=True)
14
+ u = dy.UInt8()
15
+ v = dy.UInt8()
16
+
17
+
18
+ class MyTestSchema2(dy.Schema):
19
+ shared = dy.UInt8(primary_key=True)
20
+ id = dy.UInt8(primary_key=True)
21
+ x = dy.UInt8()
22
+ y = dy.UInt8()
23
+
24
+
25
+ class MyTestCollection(dy.Collection):
26
+ a: dy.LazyFrame[MyTestSchema]
27
+ b: Annotated[
28
+ dy.LazyFrame[MyTestSchema2],
29
+ dy.CollectionMember(propagate_row_failures=True),
30
+ ]
31
+
32
+ @dy.filter()
33
+ def x_greater_u(self) -> pl.LazyFrame:
34
+ return (
35
+ self.a.join(self.b, on="shared")
36
+ .filter(pl.col("x") > pl.col("u"))
37
+ .unique("shared")
38
+ )
39
+
40
+
41
+ @pytest.fixture()
42
+ def valid_a() -> pl.LazyFrame:
43
+ return pl.LazyFrame(
44
+ [
45
+ {"shared": 1, "u": 10, "v": 5},
46
+ {"shared": 2, "u": 20, "v": 15},
47
+ {"shared": 3, "u": 30, "v": 25},
48
+ ]
49
+ )
50
+
51
+
52
+ @pytest.fixture()
53
+ def valid_b() -> pl.LazyFrame:
54
+ return pl.LazyFrame(
55
+ [
56
+ {"shared": 1, "id": 1, "x": 15, "y": 50},
57
+ {"shared": 2, "id": 1, "x": 25, "y": 60},
58
+ {"shared": 2, "id": 2, "x": 25, "y": 70},
59
+ {"shared": 3, "id": 1, "x": 5, "y": 70},
60
+ ]
61
+ )
62
+
63
+
64
+ @pytest.fixture()
65
+ def invalid_b() -> pl.LazyFrame:
66
+ return pl.LazyFrame(
67
+ [
68
+ {"shared": 1, "id": 1, "x": 15, "y": 50},
69
+ {"shared": 2, "id": 1, "x": 25, "y": 60},
70
+ {
71
+ "shared": 2,
72
+ "id": 2,
73
+ "x": 25,
74
+ "y": None,
75
+ }, # invalid row, should be propagated
76
+ {"shared": 3, "id": 1, "x": 10, "y": 70}, # filtered out due to the filter
77
+ ]
78
+ )
79
+
80
+
81
+ def test_collection_propagate_row_failures_meta() -> None:
82
+ assert MyTestCollection._failure_propagating_members() == {"b"}
83
+
84
+
85
+ def test_collection_propagate_row_failure_no_propagation(
86
+ valid_a: pl.LazyFrame,
87
+ valid_b: pl.LazyFrame,
88
+ ) -> None:
89
+ success, failures = MyTestCollection.filter(
90
+ {
91
+ "a": valid_a,
92
+ "b": valid_b,
93
+ },
94
+ cast=True,
95
+ )
96
+ # Assert that only id 3 is filtered out (caused by the filter)
97
+ assert success.a.select("shared").collect().to_series().to_list() == [1, 2]
98
+ assert success.b.select("shared").collect().to_series().to_list() == [1, 2, 2]
99
+ # Assert that nothing is filtered out due to propagation
100
+ for member_name in MyTestCollection.members().keys():
101
+ assert failures[member_name].counts()["x_greater_u"] == 1
102
+ assert len(failures[member_name].counts()) == 1
103
+
104
+
105
+ def test_collection_propagate_row_failure_with_propagation(
106
+ valid_a: pl.LazyFrame,
107
+ invalid_b: pl.LazyFrame,
108
+ ) -> None:
109
+ success, failures = MyTestCollection.filter(
110
+ {
111
+ "a": valid_a,
112
+ "b": invalid_b,
113
+ },
114
+ cast=True,
115
+ )
116
+ # Assert that id 2 is also filtered out
117
+ assert success.a.select("shared").collect().to_series().to_list() == [1]
118
+ assert success.b.select("shared").collect().to_series().to_list() == [1]
119
+ # Assert that nothing is filtered out due to propagation
120
+ for member_name in MyTestCollection.members().keys():
121
+ assert failures[member_name].counts()["x_greater_u"] == 1
122
+ assert failures[member_name].counts()["b|failure_propagation"] == 1
123
+ assert failures[member_name].counts()["x_greater_u"] == 1
124
+ assert failures[member_name].counts()["b|failure_propagation"] == 1
125
+ assert len(failures["a"].counts()) == 2
126
+ assert len(failures["b"].counts()) == 3
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes