dataframely 2.10.1__tar.gz → 2.12.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 (232) hide show
  1. {dataframely-2.10.1 → dataframely-2.12.0}/.copier-answers.yml +1 -1
  2. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/build.yml +4 -4
  3. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/chore.yml +1 -1
  4. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/ci.yml +5 -5
  5. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/copilot-setup-steps.yml +2 -2
  6. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/nightly.yml +2 -2
  7. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/release-drafter.yml +1 -1
  8. {dataframely-2.10.1 → dataframely-2.12.0}/.github/workflows/scorecard.yml +2 -2
  9. {dataframely-2.10.1 → dataframely-2.12.0}/PKG-INFO +1 -1
  10. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_polars.py +14 -5
  11. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/collection.py +99 -32
  12. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/enum.py +33 -0
  13. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/experimental/infer_schema.py +9 -0
  14. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/functional.py +2 -2
  15. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/schema.py +21 -13
  16. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/operations.rst +1 -0
  17. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/sql-generation.md +35 -0
  18. {dataframely-2.10.1 → dataframely-2.12.0}/pixi.lock +7835 -7909
  19. {dataframely-2.10.1 → dataframely-2.12.0}/pyproject.toml +1 -1
  20. {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/test_collection.py +12 -6
  21. dataframely-2.12.0/tests/collection/test_pipe.py +55 -0
  22. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_enum.py +66 -0
  23. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_sqlalchemy_columns.py +63 -1
  24. {dataframely-2.10.1 → dataframely-2.12.0}/.claude/CLAUDE.md +0 -0
  25. {dataframely-2.10.1 → dataframely-2.12.0}/.envrc +0 -0
  26. {dataframely-2.10.1 → dataframely-2.12.0}/.gitattributes +0 -0
  27. {dataframely-2.10.1 → dataframely-2.12.0}/.github/CODEOWNERS +0 -0
  28. {dataframely-2.10.1 → dataframely-2.12.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  29. {dataframely-2.10.1 → dataframely-2.12.0}/.github/copilot-instructions.md +0 -0
  30. {dataframely-2.10.1 → dataframely-2.12.0}/.github/dependabot.yml +0 -0
  31. {dataframely-2.10.1 → dataframely-2.12.0}/.github/instructions/tests.instructions.md +0 -0
  32. {dataframely-2.10.1 → dataframely-2.12.0}/.github/release-drafter.yml +0 -0
  33. {dataframely-2.10.1 → dataframely-2.12.0}/.gitignore +0 -0
  34. {dataframely-2.10.1 → dataframely-2.12.0}/.lefthook.yaml +0 -0
  35. {dataframely-2.10.1 → dataframely-2.12.0}/.prettierignore +0 -0
  36. {dataframely-2.10.1 → dataframely-2.12.0}/.prettierrc +0 -0
  37. {dataframely-2.10.1 → dataframely-2.12.0}/.readthedocs.yml +0 -0
  38. {dataframely-2.10.1 → dataframely-2.12.0}/AGENTS.md +0 -0
  39. {dataframely-2.10.1 → dataframely-2.12.0}/Cargo.lock +0 -0
  40. {dataframely-2.10.1 → dataframely-2.12.0}/Cargo.toml +0 -0
  41. {dataframely-2.10.1 → dataframely-2.12.0}/LICENSE +0 -0
  42. {dataframely-2.10.1 → dataframely-2.12.0}/README.md +0 -0
  43. {dataframely-2.10.1 → dataframely-2.12.0}/SECURITY.md +0 -0
  44. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/__init__.py +0 -0
  45. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_base_schema.py +0 -0
  46. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_compat.py +0 -0
  47. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_deprecation.py +0 -0
  48. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_filter.py +0 -0
  49. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_match_to_schema.py +0 -0
  50. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_native.pyi +0 -0
  51. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_plugin.py +0 -0
  52. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_pydantic.py +0 -0
  53. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_rule.py +0 -0
  54. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_serialization.py +0 -0
  55. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/__init__.py +0 -0
  56. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/_base.py +0 -0
  57. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/_exc.py +0 -0
  58. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/_fsspec.py +0 -0
  59. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/constants.py +0 -0
  60. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/delta.py +0 -0
  61. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_storage/parquet.py +0 -0
  62. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/_typing.py +0 -0
  63. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/__init__.py +0 -0
  64. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/_base.py +0 -0
  65. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/collection/filter_result.py +0 -0
  66. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/__init__.py +0 -0
  67. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_base.py +0 -0
  68. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_mixins.py +0 -0
  69. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_registry.py +0 -0
  70. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/_utils.py +0 -0
  71. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/any.py +0 -0
  72. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/array.py +0 -0
  73. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/binary.py +0 -0
  74. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/bool.py +0 -0
  75. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/categorical.py +0 -0
  76. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/datetime.py +0 -0
  77. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/decimal.py +0 -0
  78. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/float.py +0 -0
  79. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/integer.py +0 -0
  80. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/list.py +0 -0
  81. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/object.py +0 -0
  82. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/string.py +0 -0
  83. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/columns/struct.py +0 -0
  84. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/config.py +0 -0
  85. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/exc.py +0 -0
  86. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/experimental/__init__.py +0 -0
  87. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/filter_result.py +0 -0
  88. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/py.typed +0 -0
  89. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/random.py +0 -0
  90. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/__init__.py +0 -0
  91. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/const.py +0 -0
  92. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/factory.py +0 -0
  93. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/mask.py +0 -0
  94. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/rules.py +0 -0
  95. {dataframely-2.10.1 → dataframely-2.12.0}/dataframely/testing/storage.py +0 -0
  96. {dataframely-2.10.1 → dataframely-2.12.0}/docker-compose.yml +0 -0
  97. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_static/custom.css +0 -0
  98. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_static/favicon.ico +0 -0
  99. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/autosummary/class.rst +0 -0
  100. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/autosummary/method.rst +0 -0
  101. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/classes/column.rst +0 -0
  102. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/classes/error.rst +0 -0
  103. {dataframely-2.10.1 → dataframely-2.12.0}/docs/_templates/classes/filter_result.rst +0 -0
  104. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/generation.rst +0 -0
  105. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/index.rst +0 -0
  106. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/io.rst +0 -0
  107. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/metadata.rst +0 -0
  108. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/collection/validation.rst +0 -0
  109. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/columns/index.rst +0 -0
  110. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/errors/index.rst +0 -0
  111. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/experimental/index.rst +0 -0
  112. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/filter_result/failure_info.rst +0 -0
  113. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/filter_result/index.rst +0 -0
  114. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/index.rst +0 -0
  115. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/misc/index.rst +0 -0
  116. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/conversion.rst +0 -0
  117. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/generation.rst +0 -0
  118. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/index.rst +0 -0
  119. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/io.rst +0 -0
  120. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/metadata.rst +0 -0
  121. {dataframely-2.10.1 → dataframely-2.12.0}/docs/api/schema/validation.rst +0 -0
  122. {dataframely-2.10.1 → dataframely-2.12.0}/docs/conf.py +0 -0
  123. {dataframely-2.10.1 → dataframely-2.12.0}/docs/css/custom.css +0 -0
  124. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/coding-agents.md +0 -0
  125. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/development.md +0 -0
  126. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/examples/index.md +0 -0
  127. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/examples/real-world.ipynb +0 -0
  128. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/faq.md +0 -0
  129. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/column-metadata.md +0 -0
  130. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/data-generation.md +0 -0
  131. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/index.md +0 -0
  132. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/lazy-validation.md +0 -0
  133. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/primary-keys.md +0 -0
  134. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/features/serialization.md +0 -0
  135. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/index.md +0 -0
  136. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/migration/index.md +0 -0
  137. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/migration/v1-v2.md +0 -0
  138. {dataframely-2.10.1 → dataframely-2.12.0}/docs/guides/quickstart.md +0 -0
  139. {dataframely-2.10.1 → dataframely-2.12.0}/docs/index.md +0 -0
  140. {dataframely-2.10.1 → dataframely-2.12.0}/pixi.toml +0 -0
  141. {dataframely-2.10.1 → dataframely-2.12.0}/rust-toolchain.toml +0 -0
  142. {dataframely-2.10.1 → dataframely-2.12.0}/skills/SKILL.md +0 -0
  143. {dataframely-2.10.1 → dataframely-2.12.0}/src/lib.rs +0 -0
  144. {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/mod.rs +0 -0
  145. {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/rule_failure.rs +0 -0
  146. {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/utils.rs +0 -0
  147. {dataframely-2.10.1 → dataframely-2.12.0}/src/polars_plugin/validation_error.rs +0 -0
  148. {dataframely-2.10.1 → dataframely-2.12.0}/src/regex/errdefs.rs +0 -0
  149. {dataframely-2.10.1 → dataframely-2.12.0}/src/regex/mod.rs +0 -0
  150. {dataframely-2.10.1 → dataframely-2.12.0}/src/regex/repr.rs +0 -0
  151. {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/conftest.py +0 -0
  152. {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/test_failure.py +0 -0
  153. {dataframely-2.10.1 → dataframely-2.12.0}/tests/benches/test_schema.py +0 -0
  154. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_base.py +0 -0
  155. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_cast.py +0 -0
  156. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_collection_future_annotations.py +0 -0
  157. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_create_empty.py +0 -0
  158. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_dataframe_members.py +0 -0
  159. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_filter_one_to_n.py +0 -0
  160. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_filter_validate.py +0 -0
  161. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_ignore_in_filter.py +0 -0
  162. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_implementation.py +0 -0
  163. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_join.py +0 -0
  164. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_matches.py +0 -0
  165. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_optional_members.py +0 -0
  166. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_propagate_row_failures.py +0 -0
  167. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_repr.py +0 -0
  168. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_sample.py +0 -0
  169. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_serialization.py +0 -0
  170. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_storage.py +0 -0
  171. {dataframely-2.10.1 → dataframely-2.12.0}/tests/collection/test_validate_input.py +0 -0
  172. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/__init__.py +0 -0
  173. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_any.py +0 -0
  174. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_array.py +0 -0
  175. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_binary.py +0 -0
  176. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_datetime.py +0 -0
  177. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_decimal.py +0 -0
  178. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_float.py +0 -0
  179. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_integer.py +0 -0
  180. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_list.py +0 -0
  181. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_object.py +0 -0
  182. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_string.py +0 -0
  183. {dataframely-2.10.1 → dataframely-2.12.0}/tests/column_types/test_struct.py +0 -0
  184. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/__init__.py +0 -0
  185. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_alias.py +0 -0
  186. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_base.py +0 -0
  187. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_check.py +0 -0
  188. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_default_dtypes.py +0 -0
  189. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_description.py +0 -0
  190. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_matches.py +0 -0
  191. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_metadata.py +0 -0
  192. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_polars_schema.py +0 -0
  193. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_pyarrow.py +0 -0
  194. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_pydantic.py +0 -0
  195. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_rules.py +0 -0
  196. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_sample.py +0 -0
  197. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_str.py +0 -0
  198. {dataframely-2.10.1 → dataframely-2.12.0}/tests/columns/test_utils.py +0 -0
  199. {dataframely-2.10.1 → dataframely-2.12.0}/tests/conftest.py +0 -0
  200. {dataframely-2.10.1 → dataframely-2.12.0}/tests/core_validation/__init__.py +0 -0
  201. {dataframely-2.10.1 → dataframely-2.12.0}/tests/core_validation/test_match_to_schema.py +0 -0
  202. {dataframely-2.10.1 → dataframely-2.12.0}/tests/core_validation/test_rule_evaluation.py +0 -0
  203. {dataframely-2.10.1 → dataframely-2.12.0}/tests/experimental/test_infer_schema.py +0 -0
  204. {dataframely-2.10.1 → dataframely-2.12.0}/tests/failure_info/test_storage.py +0 -0
  205. {dataframely-2.10.1 → dataframely-2.12.0}/tests/functional/test_concat.py +0 -0
  206. {dataframely-2.10.1 → dataframely-2.12.0}/tests/functional/test_relationships.py +0 -0
  207. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_base.py +0 -0
  208. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_cast.py +0 -0
  209. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_create_empty.py +0 -0
  210. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_create_empty_if_none.py +0 -0
  211. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_filter.py +0 -0
  212. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_inheritance.py +0 -0
  213. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_matches.py +0 -0
  214. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_pydantic_model.py +0 -0
  215. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_read_write_parquet.py +0 -0
  216. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_repr.py +0 -0
  217. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_rule_implementation.py +0 -0
  218. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_sample.py +0 -0
  219. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_serialization.py +0 -0
  220. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_storage.py +0 -0
  221. {dataframely-2.10.1 → dataframely-2.12.0}/tests/schema/test_validate.py +0 -0
  222. {dataframely-2.10.1 → dataframely-2.12.0}/tests/storage/test_delta.py +0 -0
  223. {dataframely-2.10.1 → dataframely-2.12.0}/tests/storage/test_fsspec.py +0 -0
  224. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_compat.py +0 -0
  225. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_config.py +0 -0
  226. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_deprecation.py +0 -0
  227. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_factory.py +0 -0
  228. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_native_regex.py +0 -0
  229. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_pydantic.py +0 -0
  230. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_random.py +0 -0
  231. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_serialization.py +0 -0
  232. {dataframely-2.10.1 → dataframely-2.12.0}/tests/test_typing.py +0 -0
@@ -8,7 +8,7 @@
8
8
  #
9
9
  # If you want to run this as a one-off command, prefix with:
10
10
  # `pixi exec --spec copier --spec ruamel.yaml -- {command}`
11
- _commit: v0.5.2
11
+ _commit: v0.5.3
12
12
  _src_path: https://github.com/quantco/copier-template-python-open-source
13
13
  add_autobump_workflow: false
14
14
  author_email: oliver.borchert@quantco.com
@@ -13,11 +13,11 @@ jobs:
13
13
  permissions:
14
14
  contents: read
15
15
  steps:
16
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
17
17
  with:
18
18
  fetch-depth: 0
19
19
  - name: Set up pixi
20
- uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
20
+ uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
21
21
  with:
22
22
  environments: build
23
23
  - name: Set version
@@ -48,11 +48,11 @@ jobs:
48
48
  - target-platform: win-64
49
49
  os: windows-latest
50
50
  steps:
51
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
51
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
52
52
  with:
53
53
  fetch-depth: 0
54
54
  - name: Set up pixi
55
- uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
55
+ uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
56
56
  with:
57
57
  environments: build
58
58
  - name: Set version
@@ -54,6 +54,6 @@ jobs:
54
54
  pull-requests: write
55
55
  steps:
56
56
  - name: Assign labels
57
- uses: release-drafter/release-drafter/autolabeler@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0
57
+ uses: release-drafter/release-drafter/autolabeler@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0
58
58
  with:
59
59
  token: ${{ github.token }}
@@ -19,12 +19,12 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  steps:
21
21
  - name: Checkout branch
22
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
23
23
  with:
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@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
27
+ uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
28
28
  with:
29
29
  environments: default lint polars-minimal
30
30
  - name: Install Rust
@@ -63,9 +63,9 @@ jobs:
63
63
  with_optionals: false
64
64
  steps:
65
65
  - name: Checkout branch
66
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
66
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
67
67
  - name: Set up pixi
68
- uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
68
+ uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
69
69
  with:
70
70
  environments: ${{ matrix.environment }}
71
71
  # FIXME: Remove when `s3_server` fixture does not start a process anymore
@@ -79,7 +79,7 @@ jobs:
79
79
  - name: Run pytest
80
80
  run: pixi run -e ${{ matrix.environment }} test-coverage --color=yes ${{ matrix.with_optionals && '-m with_optionals' || '-m "not with_optionals"'}} --cov=dataframely --cov-report=xml
81
81
  - name: Upload codecov
82
- uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
82
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
83
83
  with:
84
84
  files: ./coverage.xml
85
85
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -13,9 +13,9 @@ jobs:
13
13
  id-token: write
14
14
  steps:
15
15
  - name: Checkout branch
16
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
16
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
17
17
  - name: Set up pixi
18
- uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
18
+ uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
19
19
  with:
20
20
  environments: default
21
21
  - name: Install Rust
@@ -23,9 +23,9 @@ jobs:
23
23
  os: [ubuntu-latest, windows-latest]
24
24
  steps:
25
25
  - name: Checkout branch
26
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
26
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
27
  - name: Set up pixi
28
- uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5
28
+ uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6
29
29
  with:
30
30
  environments: nightly
31
31
  - name: Install polars nightly
@@ -16,6 +16,6 @@ jobs:
16
16
  pull-requests: read
17
17
  steps:
18
18
  - name: Update release draft
19
- uses: release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0
19
+ uses: release-drafter/release-drafter@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7.4.0
20
20
  with:
21
21
  token: ${{ github.token }}
@@ -35,7 +35,7 @@ jobs:
35
35
 
36
36
  steps:
37
37
  - name: "Checkout code"
38
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
38
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
39
39
  with:
40
40
  persist-credentials: false
41
41
 
@@ -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@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
77
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
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.10.1
3
+ Version: 2.12.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.10
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: BSD-3-Clause
3
3
 
4
4
  import datetime as dt
5
- from typing import TypeVar
5
+ from typing import Any, TypeVar
6
6
 
7
7
  import polars as pl
8
8
  from polars.datatypes import DataTypeClass
@@ -41,9 +41,18 @@ def timedelta_matches_resolution(d: dt.timedelta, resolution: str) -> bool:
41
41
  return datetime_matches_resolution(EPOCH_DATETIME + d, resolution)
42
42
 
43
43
 
44
- def collect_if(lf: pl.LazyFrame, condition: bool) -> pl.DataFrame | pl.LazyFrame:
45
- """Collect a lazy frame if the original was eager, otherwise return the lazy
46
- frame."""
44
+ def collect_if(lf: pl.LazyFrame, condition: bool, **kwargs: Any) -> pl.LazyFrame:
45
+ """Collect a lazy frame based on `condition`."""
47
46
  if condition:
48
- return lf.collect()
47
+ return lf.collect(**kwargs).lazy()
49
48
  return lf
49
+
50
+
51
+ def collect_all_if(
52
+ lfs: dict[str, pl.LazyFrame], condition: bool, **kwargs: Any
53
+ ) -> dict[str, pl.LazyFrame]:
54
+ """Collect the lazy frames in the dictionary based on `condition`."""
55
+ if condition:
56
+ dfs = pl.collect_all(lfs.values(), **kwargs)
57
+ return {k: v.lazy() for k, v in zip(lfs.keys(), dfs)}
58
+ return lfs
@@ -8,11 +8,21 @@ import sys
8
8
  import textwrap
9
9
  import warnings
10
10
  from abc import ABC
11
- from collections.abc import Iterable, Mapping, Sequence
11
+ from collections.abc import Callable, Iterable, Mapping, Sequence
12
12
  from dataclasses import asdict
13
13
  from json import JSONDecodeError
14
14
  from pathlib import Path
15
- from typing import IO, Annotated, Any, Literal, cast, overload
15
+ from typing import (
16
+ IO,
17
+ Annotated,
18
+ Any,
19
+ Concatenate,
20
+ Literal,
21
+ ParamSpec,
22
+ TypeVar,
23
+ cast,
24
+ overload,
25
+ )
16
26
 
17
27
  import polars as pl
18
28
  import polars.exceptions as plexc
@@ -21,7 +31,7 @@ from dataframely._compat import deltalake
21
31
  from dataframely._filter import Filter
22
32
  from dataframely._native import format_rule_failures
23
33
  from dataframely._plugin import all_rules_required
24
- from dataframely._polars import FrameType, collect_if
34
+ from dataframely._polars import FrameType, collect_all_if
25
35
  from dataframely._serialization import (
26
36
  SERIALIZATION_FORMAT_VERSION,
27
37
  SchemaJSONDecoder,
@@ -53,6 +63,9 @@ else:
53
63
 
54
64
  _FILTER_COLUMN_PREFIX = "__DATAFRAMELY_FILTER_COLUMN__"
55
65
 
66
+ P = ParamSpec("P")
67
+ T = TypeVar("T")
68
+
56
69
 
57
70
  class Collection(BaseCollection, ABC):
58
71
  """Base class for all collections of data frames with a predefined schema.
@@ -367,7 +380,13 @@ class Collection(BaseCollection, ABC):
367
380
 
368
381
  @classmethod
369
382
  def validate(
370
- cls, data: Mapping[str, FrameType], /, *, cast: bool = False, eager: bool = True
383
+ cls,
384
+ data: Mapping[str, FrameType],
385
+ /,
386
+ *,
387
+ cast: bool = False,
388
+ eager: bool = True,
389
+ **kwargs: Any,
371
390
  ) -> Self:
372
391
  """Validate that a set of data frames satisfy the collection's invariants.
373
392
 
@@ -387,6 +406,8 @@ class Collection(BaseCollection, ABC):
387
406
  :meth:`~polars.LazyFrame.collect` on the individual member or
388
407
  :meth:`collect_all` on the collection. Note that, in the latter case,
389
408
  information from error messages is limited.
409
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
410
+ :meth:`polars.LazyFrame.collect` when `eager=True`.
390
411
 
391
412
  Raises:
392
413
  ValueError: If an insufficient set of input data frames is provided, i.e. if
@@ -408,7 +429,7 @@ class Collection(BaseCollection, ABC):
408
429
  if eager:
409
430
  # If we perform the validation eagerly, we call filter and check the failure
410
431
  # information to properly construct a useful error message.
411
- filtered, failures = cls.filter(data, cast=cast, eager=True)
432
+ filtered, failures = cls.filter(data, cast=cast, eager=True, **kwargs)
412
433
  if any(len(failure) > 0 for failure in failures.values()):
413
434
  errors: dict[str, str] = {}
414
435
  for member, failure in failures.items():
@@ -476,7 +497,9 @@ class Collection(BaseCollection, ABC):
476
497
  return cls._init(members)
477
498
 
478
499
  @classmethod
479
- def is_valid(cls, data: Mapping[str, FrameType], /, *, cast: bool = False) -> bool:
500
+ def is_valid(
501
+ cls, data: Mapping[str, FrameType], /, *, cast: bool = False, **kwargs: Any
502
+ ) -> bool:
480
503
  """Utility method to check whether :meth:`validate` raises an exception.
481
504
 
482
505
  Args:
@@ -485,6 +508,8 @@ class Collection(BaseCollection, ABC):
485
508
  the member as key.
486
509
  cast: Whether columns with a wrong data type in the member data frame are
487
510
  cast to their schemas' defined data types if possible.
511
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
512
+ :meth:`polars.LazyFrame.collect`.
488
513
 
489
514
  Returns:
490
515
  Whether the provided members satisfy the invariants of the collection.
@@ -499,7 +524,7 @@ class Collection(BaseCollection, ABC):
499
524
  members: dict[str, pl.LazyFrame] = {}
500
525
  for member, schema in cls.member_schemas().items():
501
526
  if member in data:
502
- if not schema.is_valid(data[member], cast=cast):
527
+ if not schema.is_valid(data[member], cast=cast, **kwargs):
503
528
  return False
504
529
  members[member] = data[member].lazy()
505
530
 
@@ -510,9 +535,12 @@ class Collection(BaseCollection, ABC):
510
535
  keep = [filter.logic(result_cls).select(primary_key) for filter in filters]
511
536
  joined = _join_all(*keep, on=primary_key, how="inner")
512
537
  removed_rows = pl.collect_all(
513
- data[member].lazy().join(joined, on=primary_key, how="anti")
514
- for member in cls.members()
515
- if member in data
538
+ (
539
+ data[member].lazy().join(joined, on=primary_key, how="anti")
540
+ for member in cls.members()
541
+ if member in data
542
+ ),
543
+ **kwargs,
516
544
  )
517
545
  return all(df.is_empty() for df in removed_rows)
518
546
 
@@ -522,7 +550,13 @@ class Collection(BaseCollection, ABC):
522
550
 
523
551
  @classmethod
524
552
  def filter(
525
- cls, data: Mapping[str, FrameType], /, *, cast: bool = False, eager: bool = True
553
+ cls,
554
+ data: Mapping[str, FrameType],
555
+ /,
556
+ *,
557
+ cast: bool = False,
558
+ eager: bool = True,
559
+ **kwargs: Any,
526
560
  ) -> CollectionFilterResult[Self]:
527
561
  """Filter the members data frame by their schemas and the collection's filters.
528
562
 
@@ -538,6 +572,8 @@ class Collection(BaseCollection, ABC):
538
572
  eager: Whether the filter operation should be performed eagerly.
539
573
  Note that until https://github.com/pola-rs/polars/pull/24129 is
540
574
  released, eagerly filtering can provide significant speedups.
575
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all` and
576
+ :meth:`polars.LazyFrame.collect` when `eager=True`.
541
577
 
542
578
  Returns:
543
579
  A named tuple with fields `result` and `failure`. The `result` field
@@ -580,7 +616,7 @@ class Collection(BaseCollection, ABC):
580
616
  continue
581
617
 
582
618
  member_result, failures[member_name] = member.schema.filter(
583
- data[member_name].lazy(), cast=cast, eager=eager
619
+ data[member_name].lazy(), cast=cast, eager=eager, **kwargs
584
620
  )
585
621
  results[member_name] = member_result.lazy()
586
622
 
@@ -592,28 +628,25 @@ class Collection(BaseCollection, ABC):
592
628
  result_cls = cls._init(results)
593
629
  primary_key = cls.common_primary_key()
594
630
 
595
- keep: dict[str, pl.LazyFrame] = {}
596
- for name, filter in filters.items():
597
- keep[name] = (
598
- filter.logic(result_cls)
599
- .select(primary_key)
600
- .pipe(collect_if, eager)
601
- .lazy()
602
- )
631
+ keep = {
632
+ name: filter.logic(result_cls).select(primary_key)
633
+ for name, filter in filters.items()
634
+ }
635
+ keep = collect_all_if(keep, eager, **kwargs)
603
636
 
604
- drop: dict[str, pl.LazyFrame] = {}
605
- for failure_propagating_member in failure_propagating_members:
606
- annotation_column = f"{failure_propagating_member}|failure_propagation"
607
- drop[annotation_column] = (
637
+ drop: dict[str, pl.LazyFrame] = {
638
+ f"{failure_propagating_member}|failure_propagation": (
608
639
  failures[failure_propagating_member]
609
640
  ._lf.select(primary_key)
610
641
  .unique()
611
- .pipe(collect_if, eager)
612
- .lazy()
613
642
  )
643
+ for failure_propagating_member in failure_propagating_members
644
+ }
645
+ drop = collect_all_if(drop, eager, **kwargs)
614
646
 
615
647
  # Now we can iterate over the results and left-join onto each individual
616
- # filter to obtain independent boolean indicators of whether to keep the row
648
+ # filter to obtain independent boolean indicators of whether to keep the row.
649
+ lfs_with_eval: dict[str, pl.LazyFrame] = {}
617
650
  for member_name, filtered in results.items():
618
651
  member_info = cls.members()[member_name]
619
652
  if member_info.ignored_in_filters:
@@ -635,7 +668,11 @@ class Collection(BaseCollection, ABC):
635
668
  maintain_order="left",
636
669
  ).with_columns(pl.col(name).fill_null(True))
637
670
 
638
- lf_with_eval = lf_with_eval.pipe(collect_if, eager).lazy()
671
+ lfs_with_eval[member_name] = lf_with_eval
672
+
673
+ lfs_with_eval = collect_all_if(lfs_with_eval, eager, **kwargs)
674
+ for member_name, lf_with_eval in lfs_with_eval.items():
675
+ member_info = cls.members()[member_name]
639
676
 
640
677
  # Filtering `lf_with_eval` by the rows for which all joins
641
678
  # "succeeded", we can identify the rows that pass all the filters. We
@@ -700,7 +737,7 @@ class Collection(BaseCollection, ABC):
700
737
 
701
738
  result = CollectionFilterResult(cls._init(results), failures)
702
739
  if eager:
703
- return result.collect_all()
740
+ return result.collect_all(**kwargs)
704
741
  return result
705
742
 
706
743
  def join(
@@ -795,22 +832,52 @@ class Collection(BaseCollection, ABC):
795
832
 
796
833
  # ---------------------------------- COLLECTION ---------------------------------- #
797
834
 
798
- def collect_all(self) -> Self:
835
+ def collect_all(self, **kwargs: Any) -> Self:
799
836
  """Collect all members of the collection.
800
837
 
801
838
  This method collects all members in parallel for maximum efficiency. It is
802
839
  particularly useful when :meth:`filter` is called with lazy frame inputs.
803
840
 
841
+ Args:
842
+ kwargs: Keyword arguments passed directly to :meth:`polars.collect_all`.
843
+
804
844
  Returns:
805
845
  The same collection with all members collected once. Members annotated
806
846
  with :class:`~dataframely.DataFrame` are returned as DataFrames, while
807
847
  members annotated with :class:`~dataframely.LazyFrame` are returned as
808
- "shallow-lazy" frames (obtained by calling ``.collect().lazy()``).
848
+ "shallow-lazy" frames (obtained by calling `.collect().lazy()`).
809
849
  """
810
850
  lazy_dict = self.to_dict()
811
- dfs = pl.collect_all(lazy_dict.values())
851
+ dfs = pl.collect_all(lazy_dict.values(), **kwargs)
812
852
  return self._init(dict(zip(lazy_dict, dfs)))
813
853
 
854
+ def pipe(
855
+ self,
856
+ function: Callable[Concatenate[Self, P], T],
857
+ *args: P.args,
858
+ **kwargs: P.kwargs,
859
+ ) -> T:
860
+ """Apply a function to this collection.
861
+
862
+ This method allows chaining operations on a collection in a fluent style,
863
+ analogously to :meth:`polars.LazyFrame.pipe`.
864
+
865
+ Args:
866
+ function: The callable to apply. It receives this collection as its first
867
+ argument, followed by any additional ``args`` and ``kwargs``.
868
+ args: Additional positional arguments to pass to ``function``.
869
+ kwargs: Additional keyword arguments to pass to ``function``.
870
+
871
+ Returns:
872
+ The return value of ``function`` when called as described.
873
+
874
+ Example:
875
+ >>> def add_prefix(collection: MyCollection, prefix: str) -> MyCollection:
876
+ ... ...
877
+ >>> result = my_collection.pipe(add_prefix, prefix="foo")
878
+ """
879
+ return function(self, *args, **kwargs)
880
+
814
881
  # --------------------------------- SERIALIZATION -------------------------------- #
815
882
 
816
883
  @classmethod
@@ -33,6 +33,8 @@ class Enum(Column):
33
33
  alias: str | None = None,
34
34
  metadata: dict[str, Any] | None = None,
35
35
  description: str | None = None,
36
+ sqlalchemy_use_enum: bool = False,
37
+ sqlalchemy_enum_name: str | None = None,
36
38
  ):
37
39
  """
38
40
  Args:
@@ -68,6 +70,15 @@ class Enum(Column):
68
70
  names, the specified alias is the only valid name.
69
71
  metadata: A dictionary of metadata to attach to the column.
70
72
  description: A human-readable description of the column.
73
+ sqlalchemy_use_enum: When ``True``, map this column to :class:`sqlalchemy.Enum`
74
+ in :meth:`~dataframely.Schema.to_sqlalchemy_columns` instead of
75
+ ``CHAR`` / ``VARCHAR``.
76
+ sqlalchemy_enum_name: Optional name for the SQLAlchemy / database enum type
77
+ when ``sqlalchemy_use_enum=True``. If omitted and ``categories`` is a
78
+ Python :class:`enum.Enum` subclass, the lowercased enum class is used.
79
+ Otherwise, the name of the column is used.
80
+ The persisted values are the enum members' ``.value`` strings (not
81
+ member names), matching :attr:`categories`.
71
82
  """
72
83
  super().__init__(
73
84
  nullable=nullable,
@@ -78,8 +89,26 @@ class Enum(Column):
78
89
  metadata=metadata,
79
90
  description=description,
80
91
  )
92
+ if sqlalchemy_enum_name and not sqlalchemy_use_enum:
93
+ raise ValueError(
94
+ "`sqlalchemy_enum_name` has no effect when `sqlalchemy_use_enum=False`."
95
+ )
96
+
97
+ self.sqlalchemy_use_enum = sqlalchemy_use_enum
98
+ self.sqlalchemy_enum_name = sqlalchemy_enum_name
81
99
  if isclass(categories) and issubclass(categories, enum.Enum):
100
+ # If the user passed an Enum type, we want to determine a default name
101
+ # based on the Enum class name, which is also what sqlalchemy does.
102
+ # One could instead keep a reference to the Enum class around and pass it
103
+ # to sqlalchemy later on, but that will interfere with the base-class implementations
104
+ # of `matches` and `to_dict` / `from_dict`.
105
+ if self.sqlalchemy_use_enum:
106
+ self.sqlalchemy_enum_name = (
107
+ self.sqlalchemy_enum_name or categories.__name__.lower()
108
+ )
109
+
82
110
  categories = (item.value for item in categories)
111
+
83
112
  self.categories = list(categories)
84
113
 
85
114
  @property
@@ -92,6 +121,10 @@ class Enum(Column):
92
121
  return self.categories == dtype.categories.to_list()
93
122
 
94
123
  def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine:
124
+ if self.sqlalchemy_use_enum:
125
+ return sa.Enum(
126
+ *self.categories, name=self.sqlalchemy_enum_name or self._name
127
+ )
95
128
  category_lengths = [len(c) for c in self.categories]
96
129
  if all(length == category_lengths[0] for length in category_lengths):
97
130
  return sa.CHAR(category_lengths[0])
@@ -9,6 +9,8 @@ import re
9
9
 
10
10
  import polars as pl
11
11
 
12
+ from dataframely._compat import _polars_version_tuple
13
+
12
14
  _POLARS_DTYPE_MAP: dict[type[pl.DataType], str] = {
13
15
  pl.Boolean: "Bool",
14
16
  pl.Int8: "Int8",
@@ -147,9 +149,16 @@ def _get_dtype_args(dtype: pl.DataType, series: pl.Series) -> list[str]:
147
149
  return [repr(dtype.categories.to_list())]
148
150
 
149
151
  if isinstance(dtype, pl.List):
152
+ if _polars_version_tuple >= (1, 36, 0):
153
+ return [_dtype_to_column_code(series.explode(empty_as_null=False))]
150
154
  return [_dtype_to_column_code(series.explode())]
151
155
 
152
156
  if isinstance(dtype, pl.Array):
157
+ if _polars_version_tuple >= (1, 36, 0):
158
+ return [
159
+ _dtype_to_column_code(series.explode(empty_as_null=False)),
160
+ f"shape={dtype.size}",
161
+ ]
153
162
  return [_dtype_to_column_code(series.explode()), f"shape={dtype.size}"]
154
163
 
155
164
  if isinstance(dtype, pl.Struct):
@@ -84,9 +84,9 @@ def require_relationship_one_to_at_least_one(
84
84
  columns, filtered to ensure a 1:{1,N} relationship.
85
85
  """
86
86
  if drop_duplicates:
87
- return lhs.unique(on, keep="none").join(rhs.unique(on), on=on)
87
+ return lhs.unique(on, keep="none").join(rhs, on=on, how="semi")
88
88
 
89
- return lhs.join(rhs.unique(on), on=on)
89
+ return lhs.join(rhs, on=on, how="semi")
90
90
 
91
91
 
92
92
  # ------------------------------------------------------------------------------------ #