dataframely 1.2.1__tar.gz → 1.3.1__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 (163) hide show
  1. {dataframely-1.2.1 → dataframely-1.3.1}/.copier-answers.yml +1 -1
  2. {dataframely-1.2.1 → dataframely-1.3.1}/.github/workflows/build.yml +1 -1
  3. {dataframely-1.2.1 → dataframely-1.3.1}/Cargo.toml +4 -0
  4. {dataframely-1.2.1 → dataframely-1.3.1}/PKG-INFO +1 -1
  5. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/__init__.py +2 -0
  6. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_base_schema.py +6 -6
  7. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_typing.py +17 -1
  8. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/__init__.py +2 -0
  9. dataframely-1.3.1/dataframely/columns/array.py +105 -0
  10. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/list.py +4 -1
  11. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/struct.py +5 -1
  12. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/mypy.py +23 -1
  13. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/random.py +5 -3
  14. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/__init__.py +2 -0
  15. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/const.py +5 -0
  16. {dataframely-1.2.1 → dataframely-1.3.1}/pixi.lock +3032 -3265
  17. {dataframely-1.2.1 → dataframely-1.3.1}/pyproject.toml +1 -1
  18. dataframely-1.3.1/tests/column_types/test_array.py +159 -0
  19. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_default_dtypes.py +1 -0
  20. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_pyarrow.py +27 -0
  21. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_sample.py +9 -1
  22. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_sql_schema.py +8 -0
  23. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_str.py +5 -0
  24. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_base.py +14 -0
  25. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_typing.py +34 -0
  26. dataframely-1.2.1/.github/workflows/update-lockfiles.yml +0 -34
  27. {dataframely-1.2.1 → dataframely-1.3.1}/.envrc +0 -0
  28. {dataframely-1.2.1 → dataframely-1.3.1}/.gitattributes +0 -0
  29. {dataframely-1.2.1 → dataframely-1.3.1}/.github/CODEOWNERS +0 -0
  30. {dataframely-1.2.1 → dataframely-1.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  31. {dataframely-1.2.1 → dataframely-1.3.1}/.github/dependabot.yml +0 -0
  32. {dataframely-1.2.1 → dataframely-1.3.1}/.github/release-drafter.yml +0 -0
  33. {dataframely-1.2.1 → dataframely-1.3.1}/.github/workflows/chore.yml +0 -0
  34. {dataframely-1.2.1 → dataframely-1.3.1}/.github/workflows/ci.yml +0 -0
  35. {dataframely-1.2.1 → dataframely-1.3.1}/.gitignore +0 -0
  36. {dataframely-1.2.1 → dataframely-1.3.1}/.pre-commit-config.yaml +0 -0
  37. {dataframely-1.2.1 → dataframely-1.3.1}/.prettierignore +0 -0
  38. {dataframely-1.2.1 → dataframely-1.3.1}/.prettierrc +0 -0
  39. {dataframely-1.2.1 → dataframely-1.3.1}/.readthedocs.yml +0 -0
  40. {dataframely-1.2.1 → dataframely-1.3.1}/Cargo.lock +0 -0
  41. {dataframely-1.2.1 → dataframely-1.3.1}/LICENSE +0 -0
  42. {dataframely-1.2.1 → dataframely-1.3.1}/README.md +0 -0
  43. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_base_collection.py +0 -0
  44. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_compat.py +0 -0
  45. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_extre.pyi +0 -0
  46. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_filter.py +0 -0
  47. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_polars.py +0 -0
  48. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_rule.py +0 -0
  49. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_validation.py +0 -0
  50. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/collection.py +0 -0
  51. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/_base.py +0 -0
  52. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/_mixins.py +0 -0
  53. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/_utils.py +0 -0
  54. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/any.py +0 -0
  55. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/bool.py +0 -0
  56. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/datetime.py +0 -0
  57. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/decimal.py +0 -0
  58. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/enum.py +0 -0
  59. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/float.py +0 -0
  60. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/integer.py +0 -0
  61. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/object.py +0 -0
  62. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/string.py +0 -0
  63. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/config.py +0 -0
  64. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/exc.py +0 -0
  65. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/failure.py +0 -0
  66. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/functional.py +0 -0
  67. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/py.typed +0 -0
  68. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/schema.py +0 -0
  69. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/factory.py +0 -0
  70. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/mask.py +0 -0
  71. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/rules.py +0 -0
  72. {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/typing.py +0 -0
  73. {dataframely-1.2.1 → dataframely-1.3.1}/docker-compose.yml +0 -0
  74. {dataframely-1.2.1 → dataframely-1.3.1}/docs/Makefile +0 -0
  75. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.collection.rst +0 -0
  76. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.any.rst +0 -0
  77. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.bool.rst +0 -0
  78. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.datetime.rst +0 -0
  79. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.decimal.rst +0 -0
  80. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.enum.rst +0 -0
  81. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.float.rst +0 -0
  82. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.integer.rst +0 -0
  83. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.list.rst +0 -0
  84. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.rst +0 -0
  85. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.string.rst +0 -0
  86. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.struct.rst +0 -0
  87. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.config.rst +0 -0
  88. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.exc.rst +0 -0
  89. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.failure.rst +0 -0
  90. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.functional.rst +0 -0
  91. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.mypy.rst +0 -0
  92. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.random.rst +0 -0
  93. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.rst +0 -0
  94. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.schema.rst +0 -0
  95. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.const.rst +0 -0
  96. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.factory.rst +0 -0
  97. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.mask.rst +0 -0
  98. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.rst +0 -0
  99. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.rules.rst +0 -0
  100. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.typing.rst +0 -0
  101. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/modules.rst +0 -0
  102. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_static/custom.css +0 -0
  103. {dataframely-1.2.1 → dataframely-1.3.1}/docs/_static/favicon.ico +0 -0
  104. {dataframely-1.2.1 → dataframely-1.3.1}/docs/conf.py +0 -0
  105. {dataframely-1.2.1 → dataframely-1.3.1}/docs/index.rst +0 -0
  106. {dataframely-1.2.1 → dataframely-1.3.1}/docs/make.bat +0 -0
  107. {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/development.rst +0 -0
  108. {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/examples/real-world.ipynb +0 -0
  109. {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/faq.rst +0 -0
  110. {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/installation.rst +0 -0
  111. {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/quickstart.rst +0 -0
  112. {dataframely-1.2.1 → dataframely-1.3.1}/pixi.toml +0 -0
  113. {dataframely-1.2.1 → dataframely-1.3.1}/src/errdefs.rs +0 -0
  114. {dataframely-1.2.1 → dataframely-1.3.1}/src/lib.rs +0 -0
  115. {dataframely-1.2.1 → dataframely-1.3.1}/src/regex_repr.rs +0 -0
  116. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_base.py +0 -0
  117. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_cast.py +0 -0
  118. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_create_empty.py +0 -0
  119. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_filter_one_to_n.py +0 -0
  120. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_filter_validate.py +0 -0
  121. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_ignore_in_filter.py +0 -0
  122. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_implementation.py +0 -0
  123. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_optional_members.py +0 -0
  124. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_sample.py +0 -0
  125. {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_validate_input.py +0 -0
  126. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/__init__.py +0 -0
  127. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_any.py +0 -0
  128. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_datetime.py +0 -0
  129. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_decimal.py +0 -0
  130. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_enum.py +0 -0
  131. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_float.py +0 -0
  132. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_integer.py +0 -0
  133. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_list.py +0 -0
  134. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_object.py +0 -0
  135. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_string.py +0 -0
  136. {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_struct.py +0 -0
  137. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/__init__.py +0 -0
  138. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_alias.py +0 -0
  139. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_check.py +0 -0
  140. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_metadata.py +0 -0
  141. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_polars_schema.py +0 -0
  142. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_rules.py +0 -0
  143. {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_utils.py +0 -0
  144. {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/__init__.py +0 -0
  145. {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/test_column_validation.py +0 -0
  146. {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/test_dtype_validation.py +0 -0
  147. {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/test_rule_evaluation.py +0 -0
  148. {dataframely-1.2.1 → dataframely-1.3.1}/tests/functional/test_concat.py +0 -0
  149. {dataframely-1.2.1 → dataframely-1.3.1}/tests/functional/test_relationships.py +0 -0
  150. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_cast.py +0 -0
  151. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_create_empty.py +0 -0
  152. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_create_empty_if_none.py +0 -0
  153. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_filter.py +0 -0
  154. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_inheritance.py +0 -0
  155. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_rule_implementation.py +0 -0
  156. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_sample.py +0 -0
  157. {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_validate.py +0 -0
  158. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_compat.py +0 -0
  159. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_config.py +0 -0
  160. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_exc.py +0 -0
  161. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_extre.py +0 -0
  162. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_failure_info.py +0 -0
  163. {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_random.py +0 -0
@@ -1,7 +1,7 @@
1
1
  # This file is managed by Copier; DO NOT EDIT OR REMOVE.
2
2
  _commit: v0.3.0
3
3
  _src_path: https://github.com/quantco/copier-template-python-open-source
4
- add_autobump_workflow: true
4
+ add_autobump_workflow: false
5
5
  author_email: oliver.borchert@quantco.com
6
6
  author_name: Oliver Borchert
7
7
  github_url: https://github.com/quantco/dataframely
@@ -78,7 +78,7 @@ jobs:
78
78
  id-token: write
79
79
  environment: pypi
80
80
  steps:
81
- - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
81
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
82
82
  with:
83
83
  path: dist
84
84
  merge-multiple: true
@@ -12,3 +12,7 @@ pyo3 = { version = "0.24", features = ["abi3-py311", "extension-module"] }
12
12
  rand = { version = "0.9", features = ["std_rng"] }
13
13
  regex-syntax = "0.8"
14
14
  thiserror = "2.0"
15
+
16
+ [profile.release]
17
+ codegen-units = 1
18
+ lto = true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataframely
3
- Version: 1.2.1
3
+ Version: 1.3.1
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -18,6 +18,7 @@ from ._typing import DataFrame, LazyFrame
18
18
  from .collection import Collection
19
19
  from .columns import (
20
20
  Any,
21
+ Array,
21
22
  Bool,
22
23
  Column,
23
24
  Date,
@@ -90,5 +91,6 @@ __all__ = [
90
91
  "String",
91
92
  "Struct",
92
93
  "List",
94
+ "Array",
93
95
  "Object",
94
96
  ]
@@ -98,7 +98,7 @@ class SchemaMeta(ABCMeta):
98
98
  )
99
99
 
100
100
  # 2) Check that the columns referenced in the group rules exist.
101
- for name, rule in result.rules.items():
101
+ for rule_name, rule in result.rules.items():
102
102
  if isinstance(rule, GroupRule):
103
103
  missing_columns = set(rule.group_columns) - set(result.columns)
104
104
  if len(missing_columns) > 0:
@@ -106,7 +106,7 @@ class SchemaMeta(ABCMeta):
106
106
  sorted(f"'{col}'" for col in missing_columns)
107
107
  )
108
108
  raise ImplementationError(
109
- f"Group validation rule '{name}' has been implemented "
109
+ f"Group validation rule '{rule_name}' has been implemented "
110
110
  f"incorrectly. It references {len(missing_columns)} columns "
111
111
  f"which are not in the schema: {missing_list}."
112
112
  )
@@ -116,16 +116,16 @@ class SchemaMeta(ABCMeta):
116
116
  # empty data frame and checking for the evaluated dtypes.
117
117
  if len(result.rules) > 0:
118
118
  lf_empty = pl.LazyFrame(
119
- schema={name: col.dtype for name, col in result.columns.items()}
119
+ schema={col_name: col.dtype for col_name, col in result.columns.items()}
120
120
  )
121
121
  # NOTE: For some reason, `polars` does not yield correct dtypes when calling
122
122
  # `collect_schema()`
123
123
  schema = with_evaluation_rules(lf_empty, result.rules).collect().schema
124
- for name, rule in result.rules.items():
125
- dtype = schema[name]
124
+ for rule_name, rule in result.rules.items():
125
+ dtype = schema[rule_name]
126
126
  if not isinstance(dtype, pl.Boolean):
127
127
  raise RuleImplementationError(
128
- name, dtype, isinstance(rule, GroupRule)
128
+ rule_name, dtype, isinstance(rule, GroupRule)
129
129
  )
130
130
 
131
131
  return super().__new__(mcs, name, bases, namespace, *args, **kwargs)
@@ -4,7 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  from collections.abc import Callable
7
- from typing import TYPE_CHECKING, Any, Generic, ParamSpec, TypeVar
7
+ from typing import TYPE_CHECKING, Any, Concatenate, Generic, ParamSpec, TypeVar
8
8
 
9
9
  import polars as pl
10
10
 
@@ -48,6 +48,14 @@ class DataFrame(pl.DataFrame, Generic[S]):
48
48
  def lazy(self, *args: Any, **kwargs: Any) -> LazyFrame[S]:
49
49
  raise NotImplementedError # pragma: no cover
50
50
 
51
+ def pipe(
52
+ self,
53
+ function: Callable[Concatenate[DataFrame[S], P], R],
54
+ *args: P.args,
55
+ **kwargs: P.kwargs,
56
+ ) -> R:
57
+ raise NotImplementedError # pragma: no cover
58
+
51
59
  @inherit_signature(pl.DataFrame.rechunk)
52
60
  def rechunk(self, *args: Any, **kwargs: Any) -> DataFrame[S]:
53
61
  raise NotImplementedError # pragma: no cover
@@ -92,6 +100,14 @@ class LazyFrame(pl.LazyFrame, Generic[S]):
92
100
  def lazy(self, *args: Any, **kwargs: Any) -> LazyFrame[S]:
93
101
  raise NotImplementedError # pragma: no cover
94
102
 
103
+ def pipe(
104
+ self,
105
+ function: Callable[Concatenate[LazyFrame[S], P], R],
106
+ *args: P.args,
107
+ **kwargs: P.kwargs,
108
+ ) -> R:
109
+ raise NotImplementedError # pragma: no cover
110
+
95
111
  @inherit_signature(pl.LazyFrame.set_sorted)
96
112
  def set_sorted(self, *args: Any, **kwargs: Any) -> LazyFrame[S]:
97
113
  raise NotImplementedError # pragma: no cover
@@ -3,6 +3,7 @@
3
3
 
4
4
  from ._base import Column
5
5
  from .any import Any
6
+ from .array import Array
6
7
  from .bool import Bool
7
8
  from .datetime import Date, Datetime, Duration, Time
8
9
  from .decimal import Decimal
@@ -17,6 +18,7 @@ from .struct import Struct
17
18
  __all__ = [
18
19
  "Column",
19
20
  "Any",
21
+ "Array",
20
22
  "Bool",
21
23
  "Date",
22
24
  "Datetime",
@@ -0,0 +1,105 @@
1
+ # Copyright (c) QuantCo 2025-2025
2
+ # SPDX-License-Identifier: BSD-3-Clause
3
+
4
+ from __future__ import annotations
5
+
6
+ import math
7
+ from collections.abc import Callable, Sequence
8
+ from typing import Any, Literal
9
+
10
+ import polars as pl
11
+
12
+ from dataframely._compat import pa, sa, sa_TypeEngine
13
+ from dataframely.random import Generator
14
+
15
+ from ._base import Column
16
+ from .struct import Struct
17
+
18
+
19
+ class Array(Column):
20
+ """A fixed-shape array column."""
21
+
22
+ def __init__(
23
+ self,
24
+ inner: Column,
25
+ shape: int | tuple[int, ...],
26
+ *,
27
+ nullable: bool = True,
28
+ # polars doesn't yet support grouping by arrays,
29
+ # see https://github.com/pola-rs/polars/issues/22574
30
+ primary_key: Literal[False] = False,
31
+ check: Callable[[pl.Expr], pl.Expr] | None = None,
32
+ alias: str | None = None,
33
+ metadata: dict[str, Any] | None = None,
34
+ ):
35
+ """
36
+ Args:
37
+ inner: The inner column type. No validation rules on the inner type are supported yet.
38
+ shape: The shape of the array.
39
+ nullable: Whether this column may contain null values.
40
+ primary_key: Whether this column is part of the primary key of the schema.
41
+ Not yet supported for the Array type.
42
+ check: A custom check to run for this column. Must return a non-aggregated
43
+ boolean expression.
44
+ alias: An overwrite for this column's name which allows for using a column
45
+ name that is not a valid Python identifier. Especially note that setting
46
+ this option does _not_ allow to refer to the column with two different
47
+ names, the specified alias is the only valid name.
48
+ metadata: A dictionary of metadata to attach to the column.
49
+ """
50
+ if inner.primary_key or (
51
+ isinstance(inner, Struct)
52
+ and any(col.primary_key for col in inner.inner.values())
53
+ ):
54
+ raise ValueError(
55
+ "`primary_key=True` is not yet supported for inner types of the Array type."
56
+ )
57
+
58
+ # We disallow validation rules on the inner type since Polars arrays currently don't support .eval(). Converting
59
+ # to a list and calling .list.eval() is possible, however, since the shape can have multiple axes, the recursive
60
+ # conversion could have significant performance impact. Hence, we simply disallow inner validation rules.
61
+ # Another option would be to allow validation rules only for sampling, but not enforce them.
62
+ if inner.validation_rules(pl.lit(None)):
63
+ raise ValueError(
64
+ "Validation rules on the inner type of Array are not yet supported."
65
+ )
66
+
67
+ super().__init__(
68
+ nullable=nullable,
69
+ primary_key=False,
70
+ check=check,
71
+ alias=alias,
72
+ metadata=metadata,
73
+ )
74
+ self.inner = inner
75
+ self.shape = shape if isinstance(shape, tuple) else (shape,)
76
+
77
+ @property
78
+ def dtype(self) -> pl.DataType:
79
+ return pl.Array(self.inner.dtype, self.shape)
80
+
81
+ def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine:
82
+ # NOTE: We might want to add support for PostgreSQL's ARRAY type or use JSON in the future.
83
+ raise NotImplementedError("SQL column cannot have 'Array' type.")
84
+
85
+ def _pyarrow_dtype_of_shape(self, shape: Sequence[int]) -> pa.DataType:
86
+ if shape:
87
+ size, *rest = shape
88
+ return pa.list_(self._pyarrow_dtype_of_shape(rest), size)
89
+ else:
90
+ return self.inner.pyarrow_dtype
91
+
92
+ @property
93
+ def pyarrow_dtype(self) -> pa.DataType:
94
+ return self._pyarrow_dtype_of_shape(self.shape)
95
+
96
+ def _sample_unchecked(self, generator: Generator, n: int) -> pl.Series:
97
+ # Sample the inner elements in a flat series
98
+ n_elements = n * math.prod(self.shape)
99
+ all_elements = self.inner.sample(generator, n_elements)
100
+
101
+ # Finally, apply a null mask
102
+ return generator._apply_null_mask(
103
+ all_elements.reshape((n, *self.shape)),
104
+ null_probability=self._null_probability,
105
+ )
@@ -147,4 +147,7 @@ class List(Column):
147
147
  chain([0], element_lengths.cum_sum()), element_lengths
148
148
  )
149
149
  ]
150
- return pl.Series(list_elements)
150
+ # Finally, apply a null mask
151
+ return generator._apply_null_mask(
152
+ pl.Series(list_elements), null_probability=self._null_probability
153
+ )
@@ -96,10 +96,14 @@ class Struct(Column):
96
96
  return pa.struct({name: col.pyarrow_dtype for name, col in self.inner.items()})
97
97
 
98
98
  def _sample_unchecked(self, generator: Generator, n: int) -> pl.Series:
99
- return (
99
+ series = (
100
100
  pl.DataFrame(
101
101
  {name: col.sample(generator, n) for name, col in self.inner.items()}
102
102
  )
103
103
  .select(pl.struct(pl.all()))
104
104
  .to_series()
105
105
  )
106
+ # Apply a null mask.
107
+ return generator._apply_null_mask(
108
+ series, null_probability=self._null_probability
109
+ )
@@ -16,6 +16,7 @@ from mypy.nodes import (
16
16
  MemberExpr,
17
17
  NameExpr,
18
18
  StrExpr,
19
+ TupleExpr,
19
20
  TypeInfo,
20
21
  Var,
21
22
  )
@@ -189,7 +190,28 @@ def _convert_dy_column_to_dtype(
189
190
  ],
190
191
  )
191
192
  return api.named_type("builtins.list")
192
- if column_type == "Any":
193
+ if column_type == "Array":
194
+ if isinstance(column_args[0], CallExpr) and isinstance(
195
+ column_args[0].callee, MemberExpr | NameExpr
196
+ ):
197
+ inner_type = _convert_dy_column_to_dtype(
198
+ api,
199
+ column_args[0].callee.name,
200
+ column_args[0].args,
201
+ )
202
+ # If the array has more than one dimension, return a list of lists of the inner type.
203
+ if len(column_args) > 1 and isinstance(column_args[1], TupleExpr):
204
+ for _ in range(len(column_args[1].items) - 1):
205
+ inner_type = api.named_type(
206
+ "builtins.list",
207
+ [inner_type],
208
+ )
209
+ return api.named_type(
210
+ "builtins.list",
211
+ [inner_type],
212
+ )
213
+ return api.named_type("builtins.list")
214
+ if column_type == "Any" or column_type == "Object":
193
215
  return AnyType(TypeOfAny.explicit)
194
216
  # If we can't infer the type, we default to `Any`.
195
217
  # This is, for example, the case for self-defined types, e.g., via `functools.partial`.
@@ -47,7 +47,7 @@ class Generator:
47
47
  Returns:
48
48
  A seed of type ``uint32``.
49
49
  """
50
- return self.numpy_generator.integers(0, 2**32)
50
+ return self.numpy_generator.integers(0, 2**32, dtype=int)
51
51
 
52
52
  # ------------------------------------ NUMBERS ----------------------------------- #
53
53
 
@@ -392,8 +392,10 @@ class Generator:
392
392
  def _apply_null_mask(self, series: pl.Series, null_probability: float) -> pl.Series:
393
393
  if null_probability == 0:
394
394
  return series
395
- null_mask = self.numpy_generator.random(series.len()) < null_probability
396
- return series.scatter(np.where(null_mask)[0], None)
395
+ null_mask = (
396
+ pl.Series(self.numpy_generator.random(series.len())) > null_probability
397
+ )
398
+ return pl.select(pl.when(null_mask).then(series)).to_series()
397
399
 
398
400
 
399
401
  # --------------------------------------- UTILS -------------------------------------- #
@@ -6,6 +6,7 @@ from .const import (
6
6
  COLUMN_TYPES,
7
7
  FLOAT_COLUMN_TYPES,
8
8
  INTEGER_COLUMN_TYPES,
9
+ NO_VALIDATION_COLUMN_TYPES,
9
10
  SUPERTYPE_COLUMN_TYPES,
10
11
  )
11
12
  from .factory import create_collection, create_collection_raw, create_schema
@@ -18,6 +19,7 @@ __all__ = [
18
19
  "FLOAT_COLUMN_TYPES",
19
20
  "INTEGER_COLUMN_TYPES",
20
21
  "SUPERTYPE_COLUMN_TYPES",
22
+ "NO_VALIDATION_COLUMN_TYPES",
21
23
  "create_collection",
22
24
  "create_collection_raw",
23
25
  "create_schema",
@@ -47,3 +47,8 @@ SUPERTYPE_COLUMN_TYPES: list[type[dc.Column]] = [
47
47
  ALL_COLUMN_TYPES: list[type[dc.Column]] = (
48
48
  [dc.Any] + COLUMN_TYPES + SUPERTYPE_COLUMN_TYPES
49
49
  )
50
+
51
+ # The following is a list of column types that, when created with default parameter values, add no validation rules.
52
+ NO_VALIDATION_COLUMN_TYPES: list[type[dc.Column]] = [
53
+ t for t in ALL_COLUMN_TYPES if t not in FLOAT_COLUMN_TYPES
54
+ ]