dataframely 2.3.1__tar.gz → 2.4.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.3.1 → dataframely-2.4.0}/.github/copilot-instructions.md +23 -0
  2. {dataframely-2.3.1 → dataframely-2.4.0}/PKG-INFO +1 -1
  3. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_base_schema.py +13 -0
  4. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/decimal.py +5 -1
  5. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/list.py +5 -2
  6. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/string.py +4 -2
  7. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/schema.py +58 -19
  8. {dataframely-2.3.1 → dataframely-2.4.0}/pyproject.toml +1 -1
  9. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_sample.py +13 -0
  10. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_decimal.py +56 -1
  11. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_list.py +13 -0
  12. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_str.py +17 -0
  13. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_base.py +16 -0
  14. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_sample.py +26 -17
  15. {dataframely-2.3.1 → dataframely-2.4.0}/.copier-answers.yml +0 -0
  16. {dataframely-2.3.1 → dataframely-2.4.0}/.envrc +0 -0
  17. {dataframely-2.3.1 → dataframely-2.4.0}/.gitattributes +0 -0
  18. {dataframely-2.3.1 → dataframely-2.4.0}/.github/CODEOWNERS +0 -0
  19. {dataframely-2.3.1 → dataframely-2.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {dataframely-2.3.1 → dataframely-2.4.0}/.github/dependabot.yml +0 -0
  21. {dataframely-2.3.1 → dataframely-2.4.0}/.github/instructions/tests.instructions.md +0 -0
  22. {dataframely-2.3.1 → dataframely-2.4.0}/.github/release-drafter.yml +0 -0
  23. {dataframely-2.3.1 → dataframely-2.4.0}/.github/workflows/build.yml +0 -0
  24. {dataframely-2.3.1 → dataframely-2.4.0}/.github/workflows/chore.yml +0 -0
  25. {dataframely-2.3.1 → dataframely-2.4.0}/.github/workflows/ci.yml +0 -0
  26. {dataframely-2.3.1 → dataframely-2.4.0}/.github/workflows/copilot-setup-steps.yml +0 -0
  27. {dataframely-2.3.1 → dataframely-2.4.0}/.github/workflows/nightly.yml +0 -0
  28. {dataframely-2.3.1 → dataframely-2.4.0}/.github/workflows/scorecard.yml +0 -0
  29. {dataframely-2.3.1 → dataframely-2.4.0}/.gitignore +0 -0
  30. {dataframely-2.3.1 → dataframely-2.4.0}/.pre-commit-config.yaml +0 -0
  31. {dataframely-2.3.1 → dataframely-2.4.0}/.prettierignore +0 -0
  32. {dataframely-2.3.1 → dataframely-2.4.0}/.prettierrc +0 -0
  33. {dataframely-2.3.1 → dataframely-2.4.0}/.readthedocs.yml +0 -0
  34. {dataframely-2.3.1 → dataframely-2.4.0}/Cargo.lock +0 -0
  35. {dataframely-2.3.1 → dataframely-2.4.0}/Cargo.toml +0 -0
  36. {dataframely-2.3.1 → dataframely-2.4.0}/LICENSE +0 -0
  37. {dataframely-2.3.1 → dataframely-2.4.0}/README.md +0 -0
  38. {dataframely-2.3.1 → dataframely-2.4.0}/SECURITY.md +0 -0
  39. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/__init__.py +0 -0
  40. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_compat.py +0 -0
  41. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_deprecation.py +0 -0
  42. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_filter.py +0 -0
  43. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_match_to_schema.py +0 -0
  44. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_native.pyi +0 -0
  45. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_plugin.py +0 -0
  46. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_polars.py +0 -0
  47. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_pydantic.py +0 -0
  48. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_rule.py +0 -0
  49. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_serialization.py +0 -0
  50. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_storage/__init__.py +0 -0
  51. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_storage/_base.py +0 -0
  52. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_storage/_exc.py +0 -0
  53. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_storage/constants.py +0 -0
  54. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_storage/delta.py +0 -0
  55. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_storage/parquet.py +0 -0
  56. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/_typing.py +0 -0
  57. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/collection/__init__.py +0 -0
  58. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/collection/_base.py +0 -0
  59. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/collection/collection.py +0 -0
  60. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/collection/filter_result.py +0 -0
  61. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/__init__.py +0 -0
  62. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/_base.py +0 -0
  63. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/_mixins.py +0 -0
  64. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/_registry.py +0 -0
  65. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/_utils.py +0 -0
  66. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/any.py +0 -0
  67. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/array.py +0 -0
  68. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/binary.py +0 -0
  69. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/bool.py +0 -0
  70. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/categorical.py +0 -0
  71. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/datetime.py +0 -0
  72. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/enum.py +0 -0
  73. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/float.py +0 -0
  74. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/integer.py +0 -0
  75. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/object.py +0 -0
  76. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/columns/struct.py +0 -0
  77. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/config.py +0 -0
  78. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/exc.py +0 -0
  79. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/filter_result.py +0 -0
  80. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/functional.py +0 -0
  81. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/py.typed +0 -0
  82. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/random.py +0 -0
  83. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/testing/__init__.py +0 -0
  84. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/testing/const.py +0 -0
  85. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/testing/factory.py +0 -0
  86. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/testing/mask.py +0 -0
  87. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/testing/rules.py +0 -0
  88. {dataframely-2.3.1 → dataframely-2.4.0}/dataframely/testing/storage.py +0 -0
  89. {dataframely-2.3.1 → dataframely-2.4.0}/docker-compose.yml +0 -0
  90. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_static/custom.css +0 -0
  91. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_static/favicon.ico +0 -0
  92. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_templates/autosummary/class.rst +0 -0
  93. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_templates/autosummary/method.rst +0 -0
  94. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_templates/classes/column.rst +0 -0
  95. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_templates/classes/error.rst +0 -0
  96. {dataframely-2.3.1 → dataframely-2.4.0}/docs/_templates/classes/filter_result.rst +0 -0
  97. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/collection/generation.rst +0 -0
  98. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/collection/index.rst +0 -0
  99. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/collection/io.rst +0 -0
  100. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/collection/metadata.rst +0 -0
  101. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/collection/operations.rst +0 -0
  102. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/collection/validation.rst +0 -0
  103. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/columns/index.rst +0 -0
  104. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/errors/index.rst +0 -0
  105. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/filter_result/failure_info.rst +0 -0
  106. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/filter_result/index.rst +0 -0
  107. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/index.rst +0 -0
  108. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/misc/index.rst +0 -0
  109. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/schema/conversion.rst +0 -0
  110. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/schema/generation.rst +0 -0
  111. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/schema/index.rst +0 -0
  112. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/schema/io.rst +0 -0
  113. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/schema/metadata.rst +0 -0
  114. {dataframely-2.3.1 → dataframely-2.4.0}/docs/api/schema/validation.rst +0 -0
  115. {dataframely-2.3.1 → dataframely-2.4.0}/docs/conf.py +0 -0
  116. {dataframely-2.3.1 → dataframely-2.4.0}/docs/css/custom.css +0 -0
  117. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/development.md +0 -0
  118. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/examples/index.md +0 -0
  119. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/examples/real-world.ipynb +0 -0
  120. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/faq.md +0 -0
  121. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/column-metadata.md +0 -0
  122. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/data-generation.md +0 -0
  123. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/index.md +0 -0
  124. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/lazy-validation.md +0 -0
  125. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/primary-keys.md +0 -0
  126. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/serialization.md +0 -0
  127. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/features/sql-generation.md +0 -0
  128. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/index.md +0 -0
  129. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/migration/index.md +0 -0
  130. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/migration/v1-v2.md +0 -0
  131. {dataframely-2.3.1 → dataframely-2.4.0}/docs/guides/quickstart.md +0 -0
  132. {dataframely-2.3.1 → dataframely-2.4.0}/docs/index.md +0 -0
  133. {dataframely-2.3.1 → dataframely-2.4.0}/pixi.lock +0 -0
  134. {dataframely-2.3.1 → dataframely-2.4.0}/pixi.toml +0 -0
  135. {dataframely-2.3.1 → dataframely-2.4.0}/rust-toolchain.toml +0 -0
  136. {dataframely-2.3.1 → dataframely-2.4.0}/src/lib.rs +0 -0
  137. {dataframely-2.3.1 → dataframely-2.4.0}/src/polars_plugin/mod.rs +0 -0
  138. {dataframely-2.3.1 → dataframely-2.4.0}/src/polars_plugin/rule_failure.rs +0 -0
  139. {dataframely-2.3.1 → dataframely-2.4.0}/src/polars_plugin/utils.rs +0 -0
  140. {dataframely-2.3.1 → dataframely-2.4.0}/src/polars_plugin/validation_error.rs +0 -0
  141. {dataframely-2.3.1 → dataframely-2.4.0}/src/regex/errdefs.rs +0 -0
  142. {dataframely-2.3.1 → dataframely-2.4.0}/src/regex/mod.rs +0 -0
  143. {dataframely-2.3.1 → dataframely-2.4.0}/src/regex/repr.rs +0 -0
  144. {dataframely-2.3.1 → dataframely-2.4.0}/tests/benches/conftest.py +0 -0
  145. {dataframely-2.3.1 → dataframely-2.4.0}/tests/benches/test_collection.py +0 -0
  146. {dataframely-2.3.1 → dataframely-2.4.0}/tests/benches/test_failure.py +0 -0
  147. {dataframely-2.3.1 → dataframely-2.4.0}/tests/benches/test_schema.py +0 -0
  148. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_base.py +0 -0
  149. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_cast.py +0 -0
  150. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_collection_future_annotations.py +0 -0
  151. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_create_empty.py +0 -0
  152. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_filter_one_to_n.py +0 -0
  153. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_filter_validate.py +0 -0
  154. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_ignore_in_filter.py +0 -0
  155. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_implementation.py +0 -0
  156. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_join.py +0 -0
  157. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_matches.py +0 -0
  158. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_optional_members.py +0 -0
  159. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_propagate_row_failures.py +0 -0
  160. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_repr.py +0 -0
  161. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_serialization.py +0 -0
  162. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_storage.py +0 -0
  163. {dataframely-2.3.1 → dataframely-2.4.0}/tests/collection/test_validate_input.py +0 -0
  164. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/__init__.py +0 -0
  165. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_any.py +0 -0
  166. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_array.py +0 -0
  167. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_binary.py +0 -0
  168. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_datetime.py +0 -0
  169. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_enum.py +0 -0
  170. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_float.py +0 -0
  171. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_integer.py +0 -0
  172. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_object.py +0 -0
  173. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_string.py +0 -0
  174. {dataframely-2.3.1 → dataframely-2.4.0}/tests/column_types/test_struct.py +0 -0
  175. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/__init__.py +0 -0
  176. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_alias.py +0 -0
  177. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_base.py +0 -0
  178. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_check.py +0 -0
  179. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_default_dtypes.py +0 -0
  180. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_matches.py +0 -0
  181. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_metadata.py +0 -0
  182. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_polars_schema.py +0 -0
  183. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_pyarrow.py +0 -0
  184. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_rules.py +0 -0
  185. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_sample.py +0 -0
  186. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_sqlalchemy_columns.py +0 -0
  187. {dataframely-2.3.1 → dataframely-2.4.0}/tests/columns/test_utils.py +0 -0
  188. {dataframely-2.3.1 → dataframely-2.4.0}/tests/conftest.py +0 -0
  189. {dataframely-2.3.1 → dataframely-2.4.0}/tests/core_validation/__init__.py +0 -0
  190. {dataframely-2.3.1 → dataframely-2.4.0}/tests/core_validation/test_match_to_schema.py +0 -0
  191. {dataframely-2.3.1 → dataframely-2.4.0}/tests/core_validation/test_rule_evaluation.py +0 -0
  192. {dataframely-2.3.1 → dataframely-2.4.0}/tests/failure_info/test_storage.py +0 -0
  193. {dataframely-2.3.1 → dataframely-2.4.0}/tests/functional/test_concat.py +0 -0
  194. {dataframely-2.3.1 → dataframely-2.4.0}/tests/functional/test_relationships.py +0 -0
  195. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_cast.py +0 -0
  196. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_create_empty.py +0 -0
  197. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_create_empty_if_none.py +0 -0
  198. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_filter.py +0 -0
  199. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_inheritance.py +0 -0
  200. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_matches.py +0 -0
  201. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_read_write_parquet.py +0 -0
  202. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_repr.py +0 -0
  203. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_rule_implementation.py +0 -0
  204. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_serialization.py +0 -0
  205. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_storage.py +0 -0
  206. {dataframely-2.3.1 → dataframely-2.4.0}/tests/schema/test_validate.py +0 -0
  207. {dataframely-2.3.1 → dataframely-2.4.0}/tests/storage/test_delta.py +0 -0
  208. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_compat.py +0 -0
  209. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_config.py +0 -0
  210. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_deprecation.py +0 -0
  211. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_factory.py +0 -0
  212. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_native_regex.py +0 -0
  213. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_pydantic.py +0 -0
  214. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_random.py +0 -0
  215. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_serialization.py +0 -0
  216. {dataframely-2.3.1 → dataframely-2.4.0}/tests/test_typing.py +0 -0
@@ -206,6 +206,29 @@ validated_df: dy.DataFrame[MySchema] = MySchema.validate(df, cast=True)
206
206
  4. **Documentation**: Update docstrings
207
207
  5. **API changes**: Ensure backward compatibility or document migration path
208
208
 
209
+ ### Pull request titles (required)
210
+
211
+ Pull request titles must follow the Conventional Commits format: `<type>[!]: <Subject>`
212
+
213
+ Allowed `type` values:
214
+
215
+ - `feat`: A new feature
216
+ - `fix`: A bug fix
217
+ - `docs`: Documentation only changes
218
+ - `style`: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
219
+ - `refactor`: A code change that neither fixes a bug nor adds a feature
220
+ - `perf`: A code change that improves performance
221
+ - `test`: Adding missing tests or correcting existing tests
222
+ - `build`: Changes that affect the build system or external dependencies
223
+ - `ci`: Changes to our CI configuration files and scripts
224
+ - `chore`: Other changes that don't modify src or test files
225
+ - `revert`: Reverts a previous commit
226
+
227
+ Additional rules:
228
+
229
+ - Use `!` only for **breaking changes**
230
+ - `Subject` must start with an **uppercase** letter and must **not** end with `.` or a trailing space
231
+
209
232
  ## Performance Considerations
210
233
 
211
234
  - Validation uses native polars expressions for performance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframely
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.10
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -162,6 +162,19 @@ class SchemaMeta(ABCMeta):
162
162
  f"Did you forget to add parentheses?"
163
163
  )
164
164
 
165
+ # Check for pl.DataType instance or type (e.g., pl.String() or pl.String instead of dy.String())
166
+ if isinstance(value, pl.DataType) or (
167
+ isinstance(value, type) and issubclass(value, pl.DataType)
168
+ ):
169
+ value_type = "instance" if isinstance(value, pl.DataType) else "type"
170
+ example = (
171
+ "pl.String()" if isinstance(value, pl.DataType) else "pl.String"
172
+ )
173
+ raise TypeError(
174
+ f"Schema member '{attr}' is a polars DataType {value_type}. "
175
+ f"Use dataframely column types (e.g., dy.String()) instead of polars types (e.g., {example})."
176
+ )
177
+
165
178
  return cls
166
179
 
167
180
  if not TYPE_CHECKING:
@@ -98,7 +98,11 @@ class Decimal(OrdinalMixin[decimal.Decimal], Column):
98
98
  return pl.Decimal(self.precision, self.scale)
99
99
 
100
100
  def validate_dtype(self, dtype: PolarsDataType) -> bool:
101
- return dtype.is_decimal()
101
+ return (
102
+ isinstance(dtype, pl.Decimal)
103
+ and dtype.scale == self.scale
104
+ and (self.precision is None or dtype.precision == self.precision)
105
+ )
102
106
 
103
107
  def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine:
104
108
  if self.scale and not self.precision:
@@ -131,9 +131,12 @@ class List(Column):
131
131
  def _sample_unchecked(self, generator: Generator, n: int) -> pl.Series:
132
132
  # First, sample the number of items per list element
133
133
  # NOTE: We default to 32 for the upper bound as we need some kind of reasonable
134
- # upper bound if none is set.
134
+ # upper bound if none is set. If min_length is greater than 32, we use
135
+ # min_length as the default upper bound instead.
136
+ min_len = self.min_length or 0
137
+ default_max = max(32, min_len)
135
138
  element_lengths = generator.sample_int(
136
- n, min=self.min_length or 0, max=(self.max_length or 32) + 1
139
+ n, min=min_len, max=(self.max_length or default_max) + 1
137
140
  )
138
141
 
139
142
  # Then, we can sample the inner elements in a flat series
@@ -14,6 +14,8 @@ from dataframely.random import Generator
14
14
  from ._base import Check, Column
15
15
  from ._registry import register
16
16
 
17
+ DEFAULT_SAMPLING_REGEX = r"[0-9a-zA-Z]"
18
+
17
19
 
18
20
  @register
19
21
  class String(Column):
@@ -126,9 +128,9 @@ class String(Column):
126
128
  str_max = f"{self.max_length}" if self.max_length is not None else ""
127
129
  # NOTE: We generate single-byte unicode characters here as validation uses
128
130
  # `len_bytes()`. Potentially we need to be more accurate at some point...
129
- regex = f"[\x01-\x7a]{{{str_min},{str_max}}}"
131
+ regex = f"{DEFAULT_SAMPLING_REGEX}{{{str_min},{str_max}}}"
130
132
  else:
131
- regex = r"[\x01-\x7a]*"
133
+ regex = rf"{DEFAULT_SAMPLING_REGEX}*"
132
134
 
133
135
  return generator.sample_string(
134
136
  n,
@@ -7,7 +7,7 @@ import json
7
7
  import sys
8
8
  import warnings
9
9
  from abc import ABC
10
- from collections.abc import Iterable, Mapping, Sequence
10
+ from collections.abc import Mapping, Sequence
11
11
  from json import JSONDecodeError
12
12
  from pathlib import Path
13
13
  from typing import IO, Any, Literal, overload
@@ -177,7 +177,7 @@ class Schema(BaseSchema, ABC):
177
177
  num_rows: int | None = None,
178
178
  *,
179
179
  overrides: (
180
- Mapping[str, Iterable[Any]] | Sequence[Mapping[str, Any]] | None
180
+ Mapping[str, Sequence[Any] | Any] | Sequence[Mapping[str, Any]] | None
181
181
  ) = None,
182
182
  generator: Generator | None = None,
183
183
  ) -> DataFrame[Self]:
@@ -234,26 +234,22 @@ class Schema(BaseSchema, ABC):
234
234
  g = generator or Generator()
235
235
 
236
236
  # Precondition: valid overrides. We put them into a data frame to remember which
237
- # values have been used in the algorithm below.
238
- if overrides:
237
+ # values have been used in the algorithm below. When the user passes a sequence
238
+ # of mappings, they do not require to have the same keys. Hence, we have to
239
+ # remember that the data frame has "holes".
240
+ missing_override_indices: dict[str, pl.Series] = {}
241
+ if overrides is not None:
239
242
  override_keys = (
240
- set(overrides) if isinstance(overrides, Mapping) else set(overrides[0])
243
+ set(overrides)
244
+ if isinstance(overrides, Mapping)
245
+ else (
246
+ set.union(*[set(o.keys()) for o in overrides])
247
+ if len(overrides) > 0
248
+ else set()
249
+ )
241
250
  )
242
- if isinstance(overrides, Sequence):
243
- # Check that overrides entries are consistent. Not necessary for mapping
244
- # overrides as polars checks the series lists upon data frame construction.
245
- inconsistent_override_keys = [
246
- index
247
- for index, current in enumerate(overrides)
248
- if set(current) != override_keys
249
- ]
250
- if len(inconsistent_override_keys) > 0:
251
- raise ValueError(
252
- "The `overrides` entries at the following indices "
253
- "do not provide the same keys as the first entry: "
254
- f"{inconsistent_override_keys}."
255
- )
256
251
 
252
+ # Check that all override keys refer to valid columns
257
253
  column_names = set(cls.column_names())
258
254
  if not override_keys.issubset(column_names):
259
255
  raise ValueError(
@@ -261,6 +257,19 @@ class Schema(BaseSchema, ABC):
261
257
  "which are not in the schema."
262
258
  )
263
259
 
260
+ # Remember the "holes" of the inputs if overrides are provided as a sequence
261
+ if isinstance(overrides, Sequence):
262
+ for key in override_keys:
263
+ indices = [
264
+ i for i, override in enumerate(overrides) if key not in override
265
+ ]
266
+ if len(indices) > 0:
267
+ missing_override_indices[key] = pl.Series(indices)
268
+
269
+ # NOTE: Even if the user-provided overrides have "holes", we can still just
270
+ # create the data frame. Polars will fill the missing values with nulls, we
271
+ # will replace them later during sampling. If we were to already replace
272
+ # them here, we would not be able to resample these values.
264
273
  values = pl.DataFrame(
265
274
  overrides,
266
275
  schema={
@@ -323,6 +332,7 @@ class Schema(BaseSchema, ABC):
323
332
  used_values=values.slice(0, 0),
324
333
  remaining_values=values,
325
334
  override_expressions=override_expressions,
335
+ missing_value_indices=missing_override_indices,
326
336
  )
327
337
 
328
338
  sampling_rounds = 1
@@ -360,6 +370,7 @@ class Schema(BaseSchema, ABC):
360
370
  used_values=used_values,
361
371
  remaining_values=remaining_values,
362
372
  override_expressions=override_expressions,
373
+ missing_value_indices=missing_override_indices,
363
374
  )
364
375
  sampling_rounds += 1
365
376
 
@@ -388,6 +399,7 @@ class Schema(BaseSchema, ABC):
388
399
  used_values: pl.DataFrame,
389
400
  remaining_values: pl.DataFrame,
390
401
  override_expressions: list[pl.Expr],
402
+ missing_value_indices: dict[str, pl.Series],
391
403
  ) -> tuple[pl.DataFrame, pl.DataFrame, pl.DataFrame]:
392
404
  """Private method to sample a data frame with the schema including subsequent
393
405
  filtering.
@@ -406,6 +418,33 @@ class Schema(BaseSchema, ABC):
406
418
  }
407
419
  )
408
420
 
421
+ # If we have missing value indices, we need to sample new values for the
422
+ # indices that overlap with indices in the remaining values and replace them
423
+ # in the sampled data frame.
424
+ for name, indices in missing_value_indices.items():
425
+ remapped_indices = (
426
+ indices.to_frame("idx")
427
+ .join(
428
+ remaining_values.select("__row_index__").with_row_index(
429
+ "__row_index_loop__"
430
+ ),
431
+ left_on="idx",
432
+ right_on="__row_index__",
433
+ )
434
+ .select("__row_index_loop__")
435
+ .to_series()
436
+ )
437
+ if (num := len(remapped_indices)) > 0:
438
+ sampled_values = cls.columns()[name].sample(generator, num)
439
+ sampled = sampled.with_columns(
440
+ sampled[name]
441
+ # NOTE: We need to sort here as `scatter` requires sorted indices.
442
+ # Due to concatenations in `remaining_values`, the indices can go
443
+ # out of order.
444
+ .scatter(remapped_indices.sort(), sampled_values)
445
+ .alias(name)
446
+ )
447
+
409
448
  combined_dataframe = pl.concat([previous_result, sampled])
410
449
  # Pre-process columns before filtering.
411
450
  combined_dataframe = combined_dataframe.with_columns(override_expressions)
@@ -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.3.1"
30
+ version = "2.4.0"
31
31
 
32
32
  [project.optional-dependencies]
33
33
  deltalake = ["deltalake"]
@@ -189,3 +189,16 @@ def test_duplicate_column_inlined_for_sampling() -> None:
189
189
  ],
190
190
  },
191
191
  )
192
+
193
+
194
+ def test_sample_override_sequence_with_missing_keys() -> None:
195
+ collection = MyCollection.sample(
196
+ overrides=[{"first": {"a": 1}, "second": [{"c": 2}, {}, {"b": 5}]}]
197
+ )
198
+ assert collection.first.collect().height == 1
199
+ assert collection.second is not None
200
+
201
+ second = collection.second.collect()
202
+ assert second.height == 3
203
+ assert second["c"].item(0) == 2
204
+ assert second["b"].item(2) == 5
@@ -66,7 +66,7 @@ def test_invalid_args(kwargs: dict[str, Any]) -> None:
66
66
 
67
67
 
68
68
  @pytest.mark.parametrize(
69
- "dtype", [pl.Decimal, pl.Decimal(12), pl.Decimal(None, 8), pl.Decimal(6, 2)]
69
+ "dtype", [pl.Decimal, pl.Decimal(12), pl.Decimal(None, 0), pl.Decimal(6, 0)]
70
70
  )
71
71
  def test_any_decimal_dtype_passes(dtype: DataTypeClass) -> None:
72
72
  df = pl.DataFrame(schema={"a": dtype})
@@ -171,3 +171,58 @@ def test_validate_range(
171
171
  actual = evaluate_rules(lf, rules_from_exprs(column.validation_rules(pl.col("a"))))
172
172
  expected = pl.LazyFrame(valid)
173
173
  assert_frame_equal(actual, expected)
174
+
175
+
176
+ @pytest.mark.parametrize(
177
+ ("schema_precision", "schema_scale", "dtype", "should_pass"),
178
+ [
179
+ # Exact match should pass
180
+ (38, 10, pl.Decimal(38, 10), True),
181
+ # Wrong scale should fail
182
+ (38, 10, pl.Decimal(38, 3), False),
183
+ # Wrong precision should fail
184
+ (10, 2, pl.Decimal(38, 2), False),
185
+ # Both wrong should fail
186
+ (10, 2, pl.Decimal(38, 5), False),
187
+ # precision=None should accept any precision with matching scale
188
+ (None, 5, pl.Decimal(10, 5), True),
189
+ (None, 5, pl.Decimal(20, 5), True),
190
+ # precision=None with wrong scale should fail
191
+ (None, 5, pl.Decimal(10, 3), False),
192
+ ],
193
+ )
194
+ def test_precision_scale_validation(
195
+ schema_precision: int | None,
196
+ schema_scale: int,
197
+ dtype: DataTypeClass,
198
+ should_pass: bool,
199
+ ) -> None:
200
+ class TestSchema(dy.Schema):
201
+ a = dy.Decimal(precision=schema_precision, scale=schema_scale)
202
+
203
+ df = pl.DataFrame(schema={"a": dtype})
204
+ assert TestSchema.is_valid(df) == should_pass
205
+
206
+
207
+ @pytest.mark.parametrize(
208
+ ("schema_precision", "schema_scale", "input_dtype"),
209
+ [
210
+ (38, 10, pl.Decimal(38, 3)),
211
+ (10, 2, pl.Decimal(38, 2)),
212
+ (10, 5, pl.Decimal(20, 3)),
213
+ ],
214
+ )
215
+ def test_precision_scale_casting(
216
+ schema_precision: int,
217
+ schema_scale: int,
218
+ input_dtype: pl.DataType,
219
+ ) -> None:
220
+ class TestSchema(dy.Schema):
221
+ a = dy.Decimal(precision=schema_precision, scale=schema_scale)
222
+
223
+ df_input = pl.DataFrame({"a": [decimal.Decimal("12.34")]}).with_columns(
224
+ pl.col("a").cast(input_dtype)
225
+ )
226
+ df_validated = TestSchema.validate(df_input, cast=True)
227
+ assert df_validated.schema["a"].precision == schema_precision # type: ignore[attr-defined]
228
+ assert df_validated.schema["a"].scale == schema_scale # type: ignore[attr-defined]
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) QuantCo 2025-2025
2
2
  # SPDX-License-Identifier: BSD-3-Clause
3
3
 
4
+ from typing import cast
5
+
4
6
  import polars as pl
5
7
  import pytest
6
8
 
@@ -179,3 +181,14 @@ def test_inner_primary_key_struct(
179
181
  _, failure = schema.filter(df)
180
182
  assert failure.counts() == {"a|primary_key": failure_count}
181
183
  assert validation_mask(df, failure).to_list() == mask
184
+
185
+
186
+ @pytest.mark.parametrize("min_length", [0, 10, 33, 100])
187
+ def test_list_sampling_with_min_length(min_length: int) -> None:
188
+ """Test that sampling works correctly when min_length > 32."""
189
+ schema = create_schema("test", {"a": dy.List(dy.Int64(), min_length=min_length)})
190
+ df = schema.sample(num_rows=10)
191
+ assert len(df) == 10
192
+ # Verify all lists have at least min_length elements
193
+ min_list_len = cast(int, df["a"].list.len().min())
194
+ assert min_list_len >= min_length
@@ -1,10 +1,13 @@
1
1
  # Copyright (c) QuantCo 2025-2025
2
2
  # SPDX-License-Identifier: BSD-3-Clause
3
+ import re
3
4
 
4
5
  import pytest
5
6
 
6
7
  import dataframely as dy
7
8
  from dataframely.columns import Column
9
+ from dataframely.columns.string import DEFAULT_SAMPLING_REGEX
10
+ from dataframely.random import Generator
8
11
  from dataframely.testing import ALL_COLUMN_TYPES
9
12
 
10
13
 
@@ -32,3 +35,17 @@ def test_string_representation_array() -> None:
32
35
  def test_string_representation_struct() -> None:
33
36
  column = dy.Struct({"a": dy.String()})
34
37
  assert str(column) == dy.Struct.__name__.lower()
38
+
39
+
40
+ @pytest.mark.parametrize("min_length", [None, 5, 10])
41
+ @pytest.mark.parametrize("max_length", [None, 20])
42
+ def test_string_sampling_without_regex(
43
+ min_length: int | None, max_length: int | None
44
+ ) -> None:
45
+ # Check that if no regex is provided, the sampled strings only use
46
+ # characters from the DEFAULT_SAMPLING_REGEX.
47
+ column = dy.String(min_length=min_length, max_length=max_length)
48
+ generator = Generator(seed=42)
49
+ sample = column.sample(generator=generator, n=1000)
50
+
51
+ assert all(re.match(f"{DEFAULT_SAMPLING_REGEX}*", value) for value in sample)
@@ -125,3 +125,19 @@ def test_user_error_column_type_not_instance() -> None:
125
125
  class MySchemaWithColumnTypeNotInstance(dy.Schema):
126
126
  a = dy.Int32(nullable=False, primary_key=True)
127
127
  b = dy.Float64 # User error: Forgot parentheses!
128
+
129
+
130
+ def test_user_error_polars_datatype_instance() -> None:
131
+ with pytest.raises(TypeError, match="polars DataType instance"):
132
+
133
+ class MySchemaWithPolarsDataTypeInstance(dy.Schema):
134
+ a = dy.Int32(nullable=False)
135
+ b = pl.String() # User error: Used pl.String() instead of dy.String()
136
+
137
+
138
+ def test_user_error_polars_datatype_type() -> None:
139
+ with pytest.raises(TypeError, match="polars DataType type"):
140
+
141
+ class MySchemaWithPolarsDataTypeType(dy.Schema):
142
+ a = dy.Int32(nullable=False)
143
+ b = pl.String # User error: Used pl.String instead of dy.String()
@@ -9,6 +9,7 @@ from polars.testing import assert_frame_equal
9
9
 
10
10
  import dataframely as dy
11
11
  from dataframely.random import Generator
12
+ from dataframely.testing import create_schema
12
13
 
13
14
 
14
15
  class MySimpleSchema(dy.Schema):
@@ -214,23 +215,6 @@ def test_sample_raises_superfluous_column_override() -> None:
214
215
  SchemaWithIrrelevantColumnPreProcessing.sample(100)
215
216
 
216
217
 
217
- def test_sample_with_inconsistent_overrides_keys_raises() -> None:
218
- with pytest.raises(
219
- ValueError,
220
- match=(
221
- r"The `overrides` entries at the following indices do not provide "
222
- r"the same keys as the first entry: \[1, 2\]."
223
- ),
224
- ):
225
- MySimpleSchema.sample(
226
- overrides=[
227
- {"a": 1, "b": "one"},
228
- {"a": 2},
229
- {"b": 2},
230
- ]
231
- )
232
-
233
-
234
218
  @pytest.mark.parametrize(
235
219
  "overrides,failed_column,failed_rule,failed_rows",
236
220
  [
@@ -252,3 +236,28 @@ def test_sample_invalid_override_values_raises(
252
236
  ):
253
237
  with dy.Config(max_sampling_iterations=100): # speed up the test
254
238
  MyAdvancedSchema.sample(overrides=overrides)
239
+
240
+
241
+ def test_sample_empty_override_sequence() -> None:
242
+ df = MySimpleSchema.sample(overrides=[])
243
+ assert len(df) == 0
244
+
245
+
246
+ def test_sample_override_sequence_with_missing_keys() -> None:
247
+ df = MySimpleSchema.sample(overrides=[{"a": 1}, {"b": "two"}])
248
+ assert df.item(0, 0) == 1
249
+ assert df.item(1, 1) == "two"
250
+ assert len(df) == 2
251
+
252
+
253
+ def test_sample_override_sequence_with_missing_keys_and_resampling() -> None:
254
+ schema = create_schema("test", {"a": dy.UInt8(primary_key=True), "b": dy.String()})
255
+ generator = Generator(seed=42)
256
+ df = schema.sample(
257
+ overrides=[{"a": i} for i in range(250)] + [{"b": "two"}, {"b": "three"}],
258
+ generator=generator,
259
+ )
260
+ assert len(df) == 252
261
+ assert all(df.item(i, 0) == i for i in range(250))
262
+ assert df.item(250, 1) == "two"
263
+ assert df.item(251, 1) == "three"
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