dataframely 2.0.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. dataframely-2.2.0/.github/copilot-instructions.md +214 -0
  2. dataframely-2.2.0/.github/instructions/tests.instructions.md +9 -0
  3. {dataframely-2.0.0 → dataframely-2.2.0}/.github/workflows/build.yml +6 -6
  4. {dataframely-2.0.0 → dataframely-2.2.0}/.github/workflows/ci.yml +2 -2
  5. dataframely-2.2.0/.github/workflows/copilot-setup-steps.yml +26 -0
  6. {dataframely-2.0.0 → dataframely-2.2.0}/.github/workflows/nightly.yml +1 -1
  7. {dataframely-2.0.0 → dataframely-2.2.0}/.github/workflows/scorecard.yml +2 -2
  8. {dataframely-2.0.0 → dataframely-2.2.0}/PKG-INFO +2 -2
  9. {dataframely-2.0.0 → dataframely-2.2.0}/README.md +1 -1
  10. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/__init__.py +2 -0
  11. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_base_schema.py +13 -9
  12. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_pydantic.py +2 -2
  13. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/collection/collection.py +92 -41
  14. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/array.py +25 -23
  15. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/list.py +37 -24
  16. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/struct.py +1 -1
  17. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/exc.py +7 -0
  18. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/filter_result.py +16 -4
  19. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/schema.py +36 -5
  20. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/testing/storage.py +103 -12
  21. {dataframely-2.0.0 → dataframely-2.2.0}/docs/conf.py +1 -0
  22. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/faq.md +1 -14
  23. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/migration/v1-v2.md +1 -1
  24. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/quickstart.md +3 -4
  25. {dataframely-2.0.0 → dataframely-2.2.0}/pixi.lock +9040 -8759
  26. {dataframely-2.0.0 → dataframely-2.2.0}/pixi.toml +1 -1
  27. {dataframely-2.0.0 → dataframely-2.2.0}/pyproject.toml +2 -2
  28. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_repr.py +2 -2
  29. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_serialization.py +35 -3
  30. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_storage.py +57 -7
  31. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_array.py +23 -13
  32. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_pyarrow.py +96 -7
  33. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_serialization.py +4 -4
  34. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_storage.py +62 -1
  35. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_typing.py +13 -0
  36. {dataframely-2.0.0 → dataframely-2.2.0}/.copier-answers.yml +0 -0
  37. {dataframely-2.0.0 → dataframely-2.2.0}/.envrc +0 -0
  38. {dataframely-2.0.0 → dataframely-2.2.0}/.gitattributes +0 -0
  39. {dataframely-2.0.0 → dataframely-2.2.0}/.github/CODEOWNERS +0 -0
  40. {dataframely-2.0.0 → dataframely-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  41. {dataframely-2.0.0 → dataframely-2.2.0}/.github/dependabot.yml +0 -0
  42. {dataframely-2.0.0 → dataframely-2.2.0}/.github/release-drafter.yml +0 -0
  43. {dataframely-2.0.0 → dataframely-2.2.0}/.github/workflows/chore.yml +0 -0
  44. {dataframely-2.0.0 → dataframely-2.2.0}/.gitignore +0 -0
  45. {dataframely-2.0.0 → dataframely-2.2.0}/.pre-commit-config.yaml +0 -0
  46. {dataframely-2.0.0 → dataframely-2.2.0}/.prettierignore +0 -0
  47. {dataframely-2.0.0 → dataframely-2.2.0}/.prettierrc +0 -0
  48. {dataframely-2.0.0 → dataframely-2.2.0}/.readthedocs.yml +0 -0
  49. {dataframely-2.0.0 → dataframely-2.2.0}/Cargo.lock +0 -0
  50. {dataframely-2.0.0 → dataframely-2.2.0}/Cargo.toml +0 -0
  51. {dataframely-2.0.0 → dataframely-2.2.0}/LICENSE +0 -0
  52. {dataframely-2.0.0 → dataframely-2.2.0}/SECURITY.md +0 -0
  53. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_compat.py +0 -0
  54. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_deprecation.py +0 -0
  55. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_filter.py +0 -0
  56. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_match_to_schema.py +0 -0
  57. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_native.pyi +0 -0
  58. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_plugin.py +0 -0
  59. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_polars.py +0 -0
  60. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_rule.py +0 -0
  61. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_serialization.py +0 -0
  62. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_storage/__init__.py +0 -0
  63. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_storage/_base.py +0 -0
  64. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_storage/_exc.py +0 -0
  65. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_storage/constants.py +0 -0
  66. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_storage/delta.py +0 -0
  67. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_storage/parquet.py +0 -0
  68. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/_typing.py +0 -0
  69. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/collection/__init__.py +0 -0
  70. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/collection/_base.py +0 -0
  71. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/collection/filter_result.py +0 -0
  72. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/__init__.py +0 -0
  73. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/_base.py +0 -0
  74. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/_mixins.py +0 -0
  75. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/_registry.py +0 -0
  76. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/_utils.py +0 -0
  77. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/any.py +0 -0
  78. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/binary.py +0 -0
  79. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/bool.py +0 -0
  80. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/categorical.py +0 -0
  81. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/datetime.py +0 -0
  82. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/decimal.py +0 -0
  83. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/enum.py +0 -0
  84. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/float.py +0 -0
  85. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/integer.py +0 -0
  86. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/object.py +0 -0
  87. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/columns/string.py +0 -0
  88. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/config.py +0 -0
  89. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/functional.py +0 -0
  90. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/py.typed +0 -0
  91. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/random.py +0 -0
  92. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/testing/__init__.py +0 -0
  93. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/testing/const.py +0 -0
  94. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/testing/factory.py +0 -0
  95. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/testing/mask.py +0 -0
  96. {dataframely-2.0.0 → dataframely-2.2.0}/dataframely/testing/rules.py +0 -0
  97. {dataframely-2.0.0 → dataframely-2.2.0}/docker-compose.yml +0 -0
  98. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_static/custom.css +0 -0
  99. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_static/favicon.ico +0 -0
  100. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_templates/autosummary/class.rst +0 -0
  101. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_templates/autosummary/method.rst +0 -0
  102. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_templates/classes/column.rst +0 -0
  103. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_templates/classes/error.rst +0 -0
  104. {dataframely-2.0.0 → dataframely-2.2.0}/docs/_templates/classes/filter_result.rst +0 -0
  105. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/collection/generation.rst +0 -0
  106. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/collection/index.rst +0 -0
  107. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/collection/io.rst +0 -0
  108. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/collection/metadata.rst +0 -0
  109. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/collection/operations.rst +0 -0
  110. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/collection/validation.rst +0 -0
  111. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/columns/index.rst +0 -0
  112. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/errors/index.rst +0 -0
  113. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/filter_result/failure_info.rst +0 -0
  114. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/filter_result/index.rst +0 -0
  115. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/index.rst +0 -0
  116. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/misc/index.rst +0 -0
  117. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/schema/conversion.rst +0 -0
  118. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/schema/generation.rst +0 -0
  119. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/schema/index.rst +0 -0
  120. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/schema/io.rst +0 -0
  121. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/schema/metadata.rst +0 -0
  122. {dataframely-2.0.0 → dataframely-2.2.0}/docs/api/schema/validation.rst +0 -0
  123. {dataframely-2.0.0 → dataframely-2.2.0}/docs/css/custom.css +0 -0
  124. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/development.md +0 -0
  125. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/examples/index.md +0 -0
  126. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/examples/real-world.ipynb +0 -0
  127. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/column-metadata.md +0 -0
  128. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/data-generation.md +0 -0
  129. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/index.md +0 -0
  130. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/lazy-validation.md +0 -0
  131. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/primary-keys.md +0 -0
  132. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/serialization.md +0 -0
  133. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/features/sql-generation.md +0 -0
  134. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/index.md +0 -0
  135. {dataframely-2.0.0 → dataframely-2.2.0}/docs/guides/migration/index.md +0 -0
  136. {dataframely-2.0.0 → dataframely-2.2.0}/docs/index.md +0 -0
  137. {dataframely-2.0.0 → dataframely-2.2.0}/rust-toolchain.toml +0 -0
  138. {dataframely-2.0.0 → dataframely-2.2.0}/src/lib.rs +0 -0
  139. {dataframely-2.0.0 → dataframely-2.2.0}/src/polars_plugin/mod.rs +0 -0
  140. {dataframely-2.0.0 → dataframely-2.2.0}/src/polars_plugin/rule_failure.rs +0 -0
  141. {dataframely-2.0.0 → dataframely-2.2.0}/src/polars_plugin/utils.rs +0 -0
  142. {dataframely-2.0.0 → dataframely-2.2.0}/src/polars_plugin/validation_error.rs +0 -0
  143. {dataframely-2.0.0 → dataframely-2.2.0}/src/regex/errdefs.rs +0 -0
  144. {dataframely-2.0.0 → dataframely-2.2.0}/src/regex/mod.rs +0 -0
  145. {dataframely-2.0.0 → dataframely-2.2.0}/src/regex/repr.rs +0 -0
  146. {dataframely-2.0.0 → dataframely-2.2.0}/tests/benches/conftest.py +0 -0
  147. {dataframely-2.0.0 → dataframely-2.2.0}/tests/benches/test_collection.py +0 -0
  148. {dataframely-2.0.0 → dataframely-2.2.0}/tests/benches/test_failure.py +0 -0
  149. {dataframely-2.0.0 → dataframely-2.2.0}/tests/benches/test_schema.py +0 -0
  150. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_base.py +0 -0
  151. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_cast.py +0 -0
  152. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_collection_future_annotations.py +0 -0
  153. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_create_empty.py +0 -0
  154. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_filter_one_to_n.py +0 -0
  155. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_filter_validate.py +0 -0
  156. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_ignore_in_filter.py +0 -0
  157. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_implementation.py +0 -0
  158. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_join.py +0 -0
  159. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_matches.py +0 -0
  160. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_optional_members.py +0 -0
  161. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_sample.py +0 -0
  162. {dataframely-2.0.0 → dataframely-2.2.0}/tests/collection/test_validate_input.py +0 -0
  163. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/__init__.py +0 -0
  164. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_any.py +0 -0
  165. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_binary.py +0 -0
  166. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_datetime.py +0 -0
  167. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_decimal.py +0 -0
  168. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_enum.py +0 -0
  169. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_float.py +0 -0
  170. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_integer.py +0 -0
  171. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_list.py +0 -0
  172. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_object.py +0 -0
  173. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_string.py +0 -0
  174. {dataframely-2.0.0 → dataframely-2.2.0}/tests/column_types/test_struct.py +0 -0
  175. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/__init__.py +0 -0
  176. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_alias.py +0 -0
  177. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_base.py +0 -0
  178. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_check.py +0 -0
  179. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_default_dtypes.py +0 -0
  180. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_matches.py +0 -0
  181. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_metadata.py +0 -0
  182. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_polars_schema.py +0 -0
  183. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_rules.py +0 -0
  184. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_sample.py +0 -0
  185. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_sqlalchemy_columns.py +0 -0
  186. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_str.py +0 -0
  187. {dataframely-2.0.0 → dataframely-2.2.0}/tests/columns/test_utils.py +0 -0
  188. {dataframely-2.0.0 → dataframely-2.2.0}/tests/conftest.py +0 -0
  189. {dataframely-2.0.0 → dataframely-2.2.0}/tests/core_validation/__init__.py +0 -0
  190. {dataframely-2.0.0 → dataframely-2.2.0}/tests/core_validation/test_match_to_schema.py +0 -0
  191. {dataframely-2.0.0 → dataframely-2.2.0}/tests/core_validation/test_rule_evaluation.py +0 -0
  192. {dataframely-2.0.0 → dataframely-2.2.0}/tests/failure_info/test_storage.py +0 -0
  193. {dataframely-2.0.0 → dataframely-2.2.0}/tests/functional/test_concat.py +0 -0
  194. {dataframely-2.0.0 → dataframely-2.2.0}/tests/functional/test_relationships.py +0 -0
  195. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_base.py +0 -0
  196. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_cast.py +0 -0
  197. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_create_empty.py +0 -0
  198. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_create_empty_if_none.py +0 -0
  199. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_filter.py +0 -0
  200. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_inheritance.py +0 -0
  201. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_matches.py +0 -0
  202. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_read_write_parquet.py +0 -0
  203. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_repr.py +0 -0
  204. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_rule_implementation.py +0 -0
  205. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_sample.py +0 -0
  206. {dataframely-2.0.0 → dataframely-2.2.0}/tests/schema/test_validate.py +0 -0
  207. {dataframely-2.0.0 → dataframely-2.2.0}/tests/storage/test_delta.py +0 -0
  208. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_compat.py +0 -0
  209. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_config.py +0 -0
  210. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_deprecation.py +0 -0
  211. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_factory.py +0 -0
  212. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_native_regex.py +0 -0
  213. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_pydantic.py +0 -0
  214. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_random.py +0 -0
  215. {dataframely-2.0.0 → dataframely-2.2.0}/tests/test_serialization.py +0 -0
@@ -0,0 +1,214 @@
1
+ # Dataframely - Coding Agent Instructions
2
+
3
+ ## Project Overview
4
+
5
+ Dataframely is a declarative, polars-native data frame validation library. It validates schemas and data content in
6
+ polars DataFrames using native polars expressions and a custom Rust-based polars plugin for high performance. It
7
+ supports validating individual data frames via `Schema` classes and interconnected data frames via `Collection` classes.
8
+
9
+ ## Tech Stack
10
+
11
+ ### Core Technologies
12
+
13
+ - **Python**: Primary language for the public API
14
+ - **Rust**: Backend for polars plugin and custom regex operations
15
+ - **Polars**: Only supported data frame library
16
+ - **pyo3 & maturin**: Rust-Python bindings and build system
17
+ - **pixi**: Primary environment and task manager (NOT pip/conda directly)
18
+
19
+ ### Build System
20
+
21
+ - **maturin**: Builds the Rust extension module `dataframely._native`
22
+ - **Cargo**: Rust dependency management
23
+ - Rust toolchain specified in `rust-toolchain.toml` with clippy and rustfmt components
24
+
25
+ ## Environment Setup
26
+
27
+ **CRITICAL**: Always use `pixi` commands - never run `pip`, `conda`, `python`, or `cargo` directly unless specifically
28
+ required for Rust-only operations.
29
+
30
+ ### Initial Setup
31
+
32
+ Unless already performed via external setup steps:
33
+
34
+ ```bash
35
+ # Install Rust toolchain
36
+ rustup show
37
+
38
+ # Install pixi environment and dependencies
39
+ pixi install
40
+
41
+ # Build and install the package locally (REQUIRED after Rust changes)
42
+ pixi run postinstall
43
+ ```
44
+
45
+ ### After Rust Code Changes
46
+
47
+ **Always run** `pixi run postinstall` after modifying any Rust code in `src/` to rebuild the native extension.
48
+
49
+ ## Development Workflow
50
+
51
+ ### Running Tests
52
+
53
+ ```bash
54
+ # Run all tests (excludes S3 tests by default)
55
+ pixi run test
56
+
57
+ # Run tests with S3 backend (requires moto server)
58
+ pixi run test -m s3
59
+
60
+ # Run specific test file or directory
61
+ pixi run test tests/schema/
62
+
63
+ # Run with coverage
64
+ pixi run test-coverage
65
+
66
+ # Run benchmarks
67
+ pixi run test-bench
68
+ ```
69
+
70
+ ### Code Quality
71
+
72
+ **NEVER** run linters/formatters directly. Use pre-commit:
73
+
74
+ ```bash
75
+ # Run all pre-commit hooks
76
+ pixi run pre-commit run
77
+ ```
78
+
79
+ Pre-commit handles:
80
+
81
+ - **Python**: ruff (lint & format), mypy (type checking), docformatter
82
+ - **Rust**: cargo fmt, cargo clippy
83
+ - **Other**: prettier (md/yml), taplo (toml), license headers, trailing whitespace
84
+
85
+ ### Building Documentation
86
+
87
+ ```bash
88
+ # Build documentation
89
+ pixi run -e docs postinstall
90
+ pixi run docs
91
+
92
+ # Open in browser (macOS)
93
+ open docs/_build/html/index.html
94
+ ```
95
+
96
+ ## Project Structure
97
+
98
+ ```
99
+ dataframely/ # Python package
100
+ schema.py # Core Schema class for DataFrame validation
101
+ collection/ # Collection class for validating multiple interconnected DataFrames
102
+ columns/ # Column type definitions (String, Integer, Float, etc.)
103
+ testing/ # Testing utilities (factories, masks, storage mocks)
104
+ _storage/ # Storage backends (Parquet, Delta Lake)
105
+ _rule.py # Rule decorator for validation rules
106
+ _plugin.py # Polars plugin registration
107
+ _native.pyi # Type stubs for Rust extension
108
+
109
+ src/ # Rust source code
110
+ lib.rs # PyO3 module definition
111
+ polars_plugin/ # Custom polars plugin for validation
112
+ regex/ # Custom regex operations
113
+
114
+ tests/ # Unit tests (mirrors dataframely/ structure)
115
+ benches/ # Benchmark tests
116
+ conftest.py # Shared pytest fixtures (including s3_server)
117
+
118
+ docs/ # Sphinx documentation
119
+ guides/ # User guides and examples
120
+ api/ # Auto-generated API reference
121
+ ```
122
+
123
+ ## Pixi Environments
124
+
125
+ Multiple environments for different purposes:
126
+
127
+ - **default**: Base Python + core dependencies
128
+ - **dev**: Includes jupyter for notebooks
129
+ - **test**: Testing dependencies (pytest, moto, boto3, etc.)
130
+ - **docs**: Documentation building (sphinx, myst-parser, etc.)
131
+ - **lint**: Linting and formatting tools
132
+ - **optionals**: Optional dependencies (pydantic, deltalake, pyarrow, sqlalchemy)
133
+ - **py310-py314**: Python version-specific environments
134
+
135
+ Use `-e <env>` to run commands in specific environments:
136
+
137
+ ```bash
138
+ pixi run -e test test
139
+ pixi run -e docs docs
140
+ ```
141
+
142
+ ## API Design Principles
143
+
144
+ ### Critical Guidelines
145
+
146
+ 1. **NO BREAKING CHANGES**: Public API must remain backward compatible
147
+ 2. **100% Test Coverage**: All new code requires tests
148
+ 3. **Documentation Required**: All public features need docstrings + API docs
149
+ 4. **Cautious API Extension**: Avoid adding to public API unless necessary
150
+
151
+ ### Public API
152
+
153
+ Public exports are in `dataframely/__init__.py`. Main components:
154
+
155
+ - **Schema classes**: `Schema` for DataFrame validation
156
+ - **Collection classes**: `Collection`, `CollectionMember` for multi-DataFrame validation
157
+ - **Column types**: `String`, `Integer`, `Float`, `Bool`, `Date`, `Datetime`, etc.
158
+ - **Decorators**: `@rule()`, `@filter()`
159
+ - **Type hints**: `DataFrame[Schema]`, `LazyFrame[Schema]`, `Validation`
160
+
161
+ ## Common Pitfalls & Solutions
162
+
163
+ ### S3 Testing
164
+
165
+ The `s3_server` fixture in `tests/conftest.py` uses `subprocess.Popen` to start moto_server on port 9999. This is a **workaround** for a polars issue with ThreadedMotoServer. When the polars issue is fixed, it should be replaced with ThreadedMotoServer (code is commented in the file).
166
+
167
+ **Note**: CI skips S3 tests by default. Run with `pixi run test -m s3` when modifying storage backends.
168
+
169
+ ## Testing Strategy
170
+
171
+ - Tests are organized by module, mirroring the `dataframely/` structure
172
+ - Use `dy.Schema.sample()` for generating test data
173
+ - Test both eager (`DataFrame`) and lazy (`LazyFrame`) execution
174
+ - S3 tests use moto server fixture from `conftest.py`
175
+ - Benchmark tests in `tests/benches/` use pytest-benchmark
176
+
177
+ ## Validation Pattern
178
+
179
+ Typical usage pattern:
180
+
181
+ ```python
182
+ class MySchema(dy.Schema):
183
+ col = dy.String(nullable=False)
184
+
185
+ @dy.rule()
186
+ def my_rule(cls) -> pl.Expr:
187
+ return pl.col("col").str.len_chars() > 0
188
+
189
+ # Validate and cast
190
+ validated_df: dy.DataFrame[MySchema] = MySchema.validate(df, cast=True)
191
+ ```
192
+
193
+ ## Key Configuration Files
194
+
195
+ - `pixi.toml`: Environment and task definitions
196
+ - `pyproject.toml`: Python package metadata, tool configurations (ruff, mypy, pytest)
197
+ - `Cargo.toml`: Rust dependencies and build settings
198
+ - `.pre-commit-config.yaml`: All code quality checks
199
+ - `rust-toolchain.toml`: Rust nightly version specification
200
+
201
+ ## When Making Changes
202
+
203
+ 1. **Python code**: Run `pixi run pre-commit run` before committing
204
+ 2. **Rust code**: Run `pixi run postinstall` to rebuild, then run tests
205
+ 3. **Tests**: Ensure `pixi run test` passes. If changes might affect storage backends, use `pixi run test -m s3`.
206
+ 4. **Documentation**: Update docstrings
207
+ 5. **API changes**: Ensure backward compatibility or document migration path
208
+
209
+ ## Performance Considerations
210
+
211
+ - Validation uses native polars expressions for performance
212
+ - Custom Rust plugin for advanced validation logic
213
+ - Lazy evaluation supported via `LazyFrame` for large datasets
214
+ - Avoid materializing data unnecessarily in validation rules
@@ -0,0 +1,9 @@
1
+ ---
2
+ applyTo: tests/**/*.py
3
+ ---
4
+
5
+ # Testing Guidelines
6
+
7
+ - Instead of writing new tests, existing tests should ideally be parametrized using `@pytest.mark.parametrize`
8
+ unless the parametrization is impractical, e.g. by adding more parameters
9
+ - Tests should not use docstrings unless they are _very_ complex
@@ -17,7 +17,7 @@ jobs:
17
17
  with:
18
18
  fetch-depth: 0
19
19
  - name: Set up pixi
20
- uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1
20
+ uses: prefix-dev/setup-pixi@28eb668aafebd9dede9d97c4ba1cd9989a4d0004 # v0.9.2
21
21
  with:
22
22
  environments: build
23
23
  - name: Set version
@@ -25,7 +25,7 @@ jobs:
25
25
  - name: Build project
26
26
  run: pixi run -e build build-sdist
27
27
  - name: Upload package
28
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
28
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
29
29
  with:
30
30
  name: sdist
31
31
  path: dist/*
@@ -42,7 +42,7 @@ jobs:
42
42
  - target-platform: linux-aarch64
43
43
  os: ubuntu-24.04-arm
44
44
  - target-platform: osx-64
45
- os: macos-13
45
+ os: macos-15-intel
46
46
  - target-platform: osx-arm64
47
47
  os: macos-latest
48
48
  - target-platform: win-64
@@ -52,7 +52,7 @@ jobs:
52
52
  with:
53
53
  fetch-depth: 0
54
54
  - name: Set up pixi
55
- uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1
55
+ uses: prefix-dev/setup-pixi@28eb668aafebd9dede9d97c4ba1cd9989a4d0004 # v0.9.2
56
56
  with:
57
57
  environments: build
58
58
  - name: Set version
@@ -70,7 +70,7 @@ jobs:
70
70
  - name: Check package
71
71
  run: pixi run -e build check-wheel
72
72
  - name: Upload package
73
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
73
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
74
74
  with:
75
75
  name: wheel-${{ matrix.target-platform }}
76
76
  path: dist/*
@@ -84,7 +84,7 @@ jobs:
84
84
  id-token: write
85
85
  environment: pypi
86
86
  steps:
87
- - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
87
+ - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
88
88
  with:
89
89
  path: dist
90
90
  merge-multiple: true
@@ -24,7 +24,7 @@ jobs:
24
24
  # needed for 'pre-commit-mirrors-insert-license'
25
25
  fetch-depth: 0
26
26
  - name: Set up pixi
27
- uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1
27
+ uses: prefix-dev/setup-pixi@28eb668aafebd9dede9d97c4ba1cd9989a4d0004 # v0.9.2
28
28
  with:
29
29
  environments: default lint
30
30
  - name: Install Rust
@@ -58,7 +58,7 @@ jobs:
58
58
  - name: Checkout branch
59
59
  uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
60
60
  - name: Set up pixi
61
- uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1
61
+ uses: prefix-dev/setup-pixi@28eb668aafebd9dede9d97c4ba1cd9989a4d0004 # v0.9.2
62
62
  with:
63
63
  environments: ${{ matrix.environment }}
64
64
  # FIXME: Remove when `s3_server` fixture does not start a process anymore
@@ -0,0 +1,26 @@
1
+ name: Copilot Setup Steps
2
+ on:
3
+ pull_request:
4
+ paths:
5
+ - .github/workflows/copilot-setup-steps.yml
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ copilot-setup-steps:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+ steps:
15
+ - name: Checkout branch
16
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
17
+ - name: Set up pixi
18
+ uses: prefix-dev/setup-pixi@28eb668aafebd9dede9d97c4ba1cd9989a4d0004 # v0.9.2
19
+ with:
20
+ environments: default
21
+ - name: Install Rust
22
+ run: rustup show
23
+ - name: Cache Rust dependencies
24
+ uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
25
+ - name: Install repository
26
+ run: pixi run postinstall
@@ -25,7 +25,7 @@ jobs:
25
25
  - name: Checkout branch
26
26
  uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
27
27
  - name: Set up pixi
28
- uses: prefix-dev/setup-pixi@194d461b21b6c5717c722ffc597fa91ed2ff29fa # v0.9.1
28
+ uses: prefix-dev/setup-pixi@28eb668aafebd9dede9d97c4ba1cd9989a4d0004 # v0.9.2
29
29
  with:
30
30
  environments: nightly
31
31
  - name: Install polars nightly
@@ -65,7 +65,7 @@ jobs:
65
65
  # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
66
66
  # format to the repository Actions tab.
67
67
  - name: "Upload artifact"
68
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
68
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
69
69
  with:
70
70
  name: SARIF file
71
71
  path: results.sarif
@@ -74,6 +74,6 @@ jobs:
74
74
  # Upload the results to GitHub's code scanning dashboard (optional).
75
75
  # Commenting out will disable upload of results to your repo's Code Scanning dashboard
76
76
  - name: "Upload to code-scanning"
77
- uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5
77
+ uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
78
78
  with:
79
79
  sarif_file: results.sarif
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframely
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.10
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -109,5 +109,5 @@ df = pl.DataFrame({
109
109
  validated_df: dy.DataFrame[HouseSchema] = HouseSchema.validate(df, cast=True)
110
110
  ```
111
111
 
112
- See more advanced usage examples in the [documentation](https://dataframely.readthedocs.io/en/latest/).
112
+ See more advanced usage examples in the [documentation](https://dataframely.readthedocs.io/stable/).
113
113
 
@@ -78,4 +78,4 @@ df = pl.DataFrame({
78
78
  validated_df: dy.DataFrame[HouseSchema] = HouseSchema.validate(df, cast=True)
79
79
  ```
80
80
 
81
- See more advanced usage examples in the [documentation](https://dataframely.readthedocs.io/en/latest/).
81
+ See more advanced usage examples in the [documentation](https://dataframely.readthedocs.io/stable/).
@@ -51,6 +51,7 @@ from .columns import (
51
51
  UInt64,
52
52
  )
53
53
  from .config import Config
54
+ from .exc import DeserializationError
54
55
  from .filter_result import FailureInfo
55
56
  from .functional import (
56
57
  concat_collection_members,
@@ -106,4 +107,5 @@ __all__ = [
106
107
  "Array",
107
108
  "Object",
108
109
  "Validation",
110
+ "DeserializationError",
109
111
  ]
@@ -8,7 +8,7 @@ import textwrap
8
8
  from abc import ABCMeta
9
9
  from copy import copy
10
10
  from dataclasses import dataclass, field
11
- from typing import Any
11
+ from typing import TYPE_CHECKING, Any
12
12
 
13
13
  import polars as pl
14
14
 
@@ -164,12 +164,16 @@ class SchemaMeta(ABCMeta):
164
164
 
165
165
  return cls
166
166
 
167
- def __getattribute__(cls, name: str) -> Any:
168
- val = super().__getattribute__(name)
169
- # Dynamically set the name of the column if it is a `Column` instance.
170
- if isinstance(val, Column):
171
- val._name = val.alias or name
172
- return val
167
+ if not TYPE_CHECKING:
168
+ # Only define __getattribute__ at runtime to allow type checkers to properly
169
+ # validate attribute access. When TYPE_CHECKING is True, type checkers will use
170
+ # the default metaclass behavior which correctly identifies non-existent attributes.
171
+ def __getattribute__(cls, name: str) -> Any:
172
+ val = super().__getattribute__(name)
173
+ # Dynamically set the name of the column if it is a `Column` instance.
174
+ if isinstance(val, Column):
175
+ val._name = val.alias or name
176
+ return val
173
177
 
174
178
  @staticmethod
175
179
  def _get_metadata_recursively(kls: type[object]) -> Metadata:
@@ -199,9 +203,9 @@ class SchemaMeta(ABCMeta):
199
203
  def __repr__(cls) -> str:
200
204
  parts = [f'[Schema "{cls.__name__}"]']
201
205
  parts.append(textwrap.indent("Columns:", prefix=" " * 2))
202
- for name, col in cls.columns().items():
206
+ for name, col in cls.columns().items(): # type: ignore[attr-defined]
203
207
  parts.append(textwrap.indent(f'- "{name}": {col!r}', prefix=" " * 4))
204
- if validation_rules := cls._schema_validation_rules():
208
+ if validation_rules := cls._schema_validation_rules(): # type: ignore[attr-defined]
205
209
  parts.append(textwrap.indent("Rules:", prefix=" " * 2))
206
210
  for name, rule in validation_rules.items():
207
211
  parts.append(textwrap.indent(f'- "{name}": {rule!r}', prefix=" " * 4))
@@ -20,12 +20,12 @@ _S = TypeVar("_S", bound=BaseSchema)
20
20
  def _dict_to_df(schema_type: type[BaseSchema], data: dict) -> pl.DataFrame:
21
21
  return pl.from_dict(
22
22
  data,
23
- schema=schema_type.to_polars_schema(),
23
+ schema=schema_type.to_polars_schema(), # type: ignore[attr-defined]
24
24
  )
25
25
 
26
26
 
27
27
  def _validate_df_schema(schema_type: type[_S], df: pl.DataFrame) -> DataFrame[_S]:
28
- if not schema_type.is_valid(df):
28
+ if not schema_type.is_valid(df): # type: ignore[attr-defined]
29
29
  raise ValueError("DataFrame violates schema")
30
30
  return df # type: ignore
31
31
 
@@ -12,7 +12,7 @@ from collections.abc import 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
15
+ from typing import IO, Annotated, Any, Literal, cast, overload
16
16
 
17
17
  import polars as pl
18
18
  import polars.exceptions as plexc
@@ -33,7 +33,11 @@ from dataframely._storage.constants import COLLECTION_METADATA_KEY
33
33
  from dataframely._storage.delta import DeltaStorageBackend
34
34
  from dataframely._storage.parquet import ParquetStorageBackend
35
35
  from dataframely._typing import LazyFrame, Validation
36
- from dataframely.exc import ValidationError, ValidationRequiredError
36
+ from dataframely.exc import (
37
+ DeserializationError,
38
+ ValidationError,
39
+ ValidationRequiredError,
40
+ )
37
41
  from dataframely.filter_result import FailureInfo
38
42
  from dataframely.random import Generator
39
43
  from dataframely.schema import _schema_from_dict
@@ -531,6 +535,25 @@ class Collection(BaseCollection, ABC):
531
535
  Raises:
532
536
  ValueError: If an insufficient set of input data frames is provided, i.e. if
533
537
  any required member of this collection is missing in the input.
538
+
539
+ Example:
540
+
541
+ .. code-block:: python
542
+
543
+ # Define collection
544
+ class HospitalInvoiceData(dy.Collection):
545
+ invoice: dy.LazyFrame[InvoiceSchema]
546
+ ...
547
+
548
+ # Filter the data and cast columns to expected types
549
+ good, failure = HospitalInvoiceData.filter(df, cast=True)
550
+
551
+ # Inspect the reasons for the failed rows for member `invoice`
552
+ print(failure.invoice.counts())
553
+
554
+ # Inspect the failed rows
555
+ failed_df = failure.invoice.invalid()
556
+ print(failed_df)
534
557
  """
535
558
  cls._validate_input_keys(data)
536
559
 
@@ -872,13 +895,13 @@ class Collection(BaseCollection, ABC):
872
895
  - `"allow"`: The method tries to read the schema data from the parquet
873
896
  files. If the stored collection schema matches this collection
874
897
  schema, the collection is read without validation. If the stored
875
- schema mismatches this schema no metadata can be found in
898
+ schema mismatches this schema, no valid metadata can be found in
876
899
  the parquets, or the files have conflicting metadata,
877
900
  this method automatically runs :meth:`validate` with `cast=True`.
878
901
  - `"warn"`: The method behaves similarly to `"allow"`. However,
879
902
  it prints a warning if validation is necessary.
880
903
  - `"forbid"`: The method never runs validation automatically and only
881
- returns if the metadata stores a collection schema that matches
904
+ returns if the metadata stores a valid collection schema that matches
882
905
  this collection.
883
906
  - `"skip"`: The method never runs validation and simply reads the
884
907
  data, entrusting the user that the schema is valid. *Use this option
@@ -1165,7 +1188,12 @@ class Collection(BaseCollection, ABC):
1165
1188
  members=cls.member_schemas().keys(), **kwargs
1166
1189
  )
1167
1190
 
1168
- collection_types = _deserialize_types(serialized_collection_types)
1191
+ # Use strict=False when validation is "allow", "warn" or "skip" to tolerate
1192
+ # missing or broken collection metadata.
1193
+ strict = validation == "forbid"
1194
+ collection_types = _deserialize_types(
1195
+ serialized_collection_types, strict=strict
1196
+ )
1169
1197
  collection_type = _reconcile_collection_types(collection_types)
1170
1198
 
1171
1199
  if cls._requires_validation_for_reading_parquets(collection_type, validation):
@@ -1226,14 +1254,27 @@ def read_parquet_metadata_collection(
1226
1254
  """
1227
1255
  metadata = pl.read_parquet_metadata(source)
1228
1256
  if (schema_metadata := metadata.get(COLLECTION_METADATA_KEY)) is not None:
1229
- try:
1230
- return deserialize_collection(schema_metadata)
1231
- except (JSONDecodeError, plexc.ComputeError):
1232
- return None
1257
+ return deserialize_collection(schema_metadata, strict=False)
1233
1258
  return None
1234
1259
 
1235
1260
 
1236
- def deserialize_collection(data: str) -> type[Collection]:
1261
+ @overload
1262
+ def deserialize_collection(
1263
+ data: str, strict: Literal[True] = True
1264
+ ) -> type[Collection]: ...
1265
+
1266
+
1267
+ @overload
1268
+ def deserialize_collection(
1269
+ data: str, strict: Literal[False]
1270
+ ) -> type[Collection] | None: ...
1271
+
1272
+
1273
+ @overload
1274
+ def deserialize_collection(data: str, strict: bool) -> type[Collection] | None: ...
1275
+
1276
+
1277
+ def deserialize_collection(data: str, strict: bool = True) -> type[Collection] | None:
1237
1278
  """Deserialize a collection from a JSON string.
1238
1279
 
1239
1280
  This method allows to dynamically load a collection from its serialization, without
@@ -1241,12 +1282,14 @@ def deserialize_collection(data: str) -> type[Collection]:
1241
1282
 
1242
1283
  Args:
1243
1284
  data: The JSON string created via :meth:`Collection.serialize`.
1285
+ strict: Whether to raise an exception if the collection cannot be deserialized.
1244
1286
 
1245
1287
  Returns:
1246
1288
  The collection loaded from the JSON data.
1247
1289
 
1248
1290
  Raises:
1249
- ValueError: If the schema format version is not supported.
1291
+ DeserializationError: If the collection can not be deserialized
1292
+ and `strict=True`.
1250
1293
 
1251
1294
  Attention:
1252
1295
  The returned collection **cannot** be used to create instances of the
@@ -1261,34 +1304,41 @@ def deserialize_collection(data: str) -> type[Collection]:
1261
1304
  See also:
1262
1305
  :meth:`Collection.serialize` for additional information on serialization.
1263
1306
  """
1264
- decoded = json.loads(data, cls=SchemaJSONDecoder)
1265
- if (format := decoded["versions"]["format"]) != SERIALIZATION_FORMAT_VERSION:
1266
- raise ValueError(f"Unsupported schema format version: {format}")
1267
-
1268
- annotations: dict[str, Any] = {}
1269
- for name, info in decoded["members"].items():
1270
- lf_type = LazyFrame[_schema_from_dict(info["schema"])] # type: ignore
1271
- if info["is_optional"]:
1272
- lf_type = lf_type | None # type: ignore
1273
- annotations[name] = Annotated[
1274
- lf_type,
1275
- CollectionMember(
1276
- ignored_in_filters=info["ignored_in_filters"],
1277
- inline_for_sampling=info["inline_for_sampling"],
1278
- ),
1279
- ]
1280
-
1281
- return type(
1282
- f"{decoded['name']}_dynamic",
1283
- (Collection,),
1284
- {
1285
- "__annotations__": annotations,
1286
- **{
1287
- name: Filter(logic=lambda _, logic=logic: logic) # type: ignore
1288
- for name, logic in decoded["filters"].items()
1307
+ try:
1308
+ decoded = json.loads(data, cls=SchemaJSONDecoder)
1309
+ if (format := decoded["versions"]["format"]) != SERIALIZATION_FORMAT_VERSION:
1310
+ raise ValueError(f"Unsupported schema format version: {format}")
1311
+
1312
+ annotations: dict[str, Any] = {}
1313
+ for name, info in decoded["members"].items():
1314
+ lf_type = LazyFrame[_schema_from_dict(info["schema"])] # type: ignore
1315
+ if info["is_optional"]:
1316
+ lf_type = lf_type | None # type: ignore
1317
+ annotations[name] = Annotated[
1318
+ lf_type,
1319
+ CollectionMember(
1320
+ ignored_in_filters=info["ignored_in_filters"],
1321
+ inline_for_sampling=info["inline_for_sampling"],
1322
+ ),
1323
+ ]
1324
+
1325
+ return type(
1326
+ f"{decoded['name']}_dynamic",
1327
+ (Collection,),
1328
+ {
1329
+ "__annotations__": annotations,
1330
+ **{
1331
+ name: Filter(logic=lambda _, logic=logic: logic) # type: ignore
1332
+ for name, logic in decoded["filters"].items()
1333
+ },
1289
1334
  },
1290
- },
1291
- )
1335
+ )
1336
+ except (ValueError, TypeError, JSONDecodeError, plexc.ComputeError) as e:
1337
+ if strict:
1338
+ raise DeserializationError(
1339
+ "The Collection metadata could not be deserialized"
1340
+ ) from e
1341
+ return None
1292
1342
 
1293
1343
 
1294
1344
  # --------------------------------------- UTILS -------------------------------------- #
@@ -1314,14 +1364,15 @@ def _extract_keys_if_exist(
1314
1364
 
1315
1365
  def _deserialize_types(
1316
1366
  serialized_collection_types: Iterable[str | None],
1367
+ strict: bool = True,
1317
1368
  ) -> list[type[Collection]]:
1318
1369
  collection_types = []
1319
- collection_type: type[Collection] | None = None
1320
1370
  for t in serialized_collection_types:
1321
1371
  if t is None:
1322
1372
  continue
1323
- collection_type = deserialize_collection(t)
1324
- collection_types.append(collection_type)
1373
+ collection_type = deserialize_collection(t, strict=strict)
1374
+ if collection_type is not None:
1375
+ collection_types.append(collection_type)
1325
1376
 
1326
1377
  return collection_types
1327
1378