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.
- {dataframely-1.2.1 → dataframely-1.3.1}/.copier-answers.yml +1 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/workflows/build.yml +1 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/Cargo.toml +4 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/PKG-INFO +1 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/__init__.py +2 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_base_schema.py +6 -6
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_typing.py +17 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/__init__.py +2 -0
- dataframely-1.3.1/dataframely/columns/array.py +105 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/list.py +4 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/struct.py +5 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/mypy.py +23 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/random.py +5 -3
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/__init__.py +2 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/const.py +5 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/pixi.lock +3032 -3265
- {dataframely-1.2.1 → dataframely-1.3.1}/pyproject.toml +1 -1
- dataframely-1.3.1/tests/column_types/test_array.py +159 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_default_dtypes.py +1 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_pyarrow.py +27 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_sample.py +9 -1
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_sql_schema.py +8 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_str.py +5 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_base.py +14 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_typing.py +34 -0
- dataframely-1.2.1/.github/workflows/update-lockfiles.yml +0 -34
- {dataframely-1.2.1 → dataframely-1.3.1}/.envrc +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.gitattributes +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/CODEOWNERS +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/dependabot.yml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/release-drafter.yml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/workflows/chore.yml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.github/workflows/ci.yml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.gitignore +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.pre-commit-config.yaml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.prettierignore +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.prettierrc +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/.readthedocs.yml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/Cargo.lock +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/LICENSE +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/README.md +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_base_collection.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_compat.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_extre.pyi +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_filter.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_polars.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_rule.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/_validation.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/collection.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/_base.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/_mixins.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/_utils.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/any.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/bool.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/datetime.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/decimal.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/enum.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/float.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/integer.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/object.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/columns/string.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/config.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/exc.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/failure.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/functional.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/py.typed +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/schema.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/factory.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/mask.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/rules.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/dataframely/testing/typing.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docker-compose.yml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/Makefile +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.collection.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.any.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.bool.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.datetime.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.decimal.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.enum.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.float.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.integer.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.list.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.string.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.columns.struct.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.config.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.exc.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.failure.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.functional.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.mypy.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.random.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.schema.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.const.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.factory.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.mask.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.rules.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/dataframely.testing.typing.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_api/modules.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_static/custom.css +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/_static/favicon.ico +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/conf.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/index.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/make.bat +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/development.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/examples/real-world.ipynb +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/faq.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/installation.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/docs/sites/quickstart.rst +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/pixi.toml +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/src/errdefs.rs +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/src/lib.rs +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/src/regex_repr.rs +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_base.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_cast.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_create_empty.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_filter_one_to_n.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_filter_validate.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_ignore_in_filter.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_implementation.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_optional_members.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_sample.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/collection/test_validate_input.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/__init__.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_any.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_datetime.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_decimal.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_enum.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_float.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_integer.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_list.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_object.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_string.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/column_types/test_struct.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/__init__.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_alias.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_check.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_metadata.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_polars_schema.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_rules.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/columns/test_utils.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/__init__.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/test_column_validation.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/test_dtype_validation.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/core_validation/test_rule_evaluation.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/functional/test_concat.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/functional/test_relationships.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_cast.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_create_empty.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_create_empty_if_none.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_filter.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_inheritance.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_rule_implementation.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_sample.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/schema/test_validate.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_compat.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_config.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_exc.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_extre.py +0 -0
- {dataframely-1.2.1 → dataframely-1.3.1}/tests/test_failure_info.py +0 -0
- {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:
|
|
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@
|
|
81
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
82
82
|
with:
|
|
83
83
|
path: dist
|
|
84
84
|
merge-multiple: true
|
|
@@ -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
|
|
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 '{
|
|
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={
|
|
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
|
|
125
|
-
dtype = schema[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 == "
|
|
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 =
|
|
396
|
-
|
|
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
|
+
]
|