From f83a412398fe0d99213da479498ef1022b6c445e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:17:30 +0000 Subject: [PATCH 1/5] Initial plan From 73a3057998dc69ebe0a21735db0f84e90c2395cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:27:38 +0000 Subject: [PATCH 2/5] Fix Decimal column to validate precision and scale Co-authored-by: borchero <22455425+borchero@users.noreply.github.com> --- dataframely/columns/decimal.py | 10 +++++- tests/column_types/test_decimal.py | 57 +++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/dataframely/columns/decimal.py b/dataframely/columns/decimal.py index 9a072ea..07db592 100644 --- a/dataframely/columns/decimal.py +++ b/dataframely/columns/decimal.py @@ -98,7 +98,15 @@ def dtype(self) -> pl.DataType: return pl.Decimal(self.precision, self.scale) def validate_dtype(self, dtype: PolarsDataType) -> bool: - return dtype.is_decimal() + if not dtype.is_decimal(): + return False + # Scale must always match + if dtype.scale != self.scale: + return False + # Precision must match if explicitly specified in the schema + if self.precision is not None and dtype.precision != self.precision: + return False + return True def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine: if self.scale and not self.precision: diff --git a/tests/column_types/test_decimal.py b/tests/column_types/test_decimal.py index 11719e4..e96b507 100644 --- a/tests/column_types/test_decimal.py +++ b/tests/column_types/test_decimal.py @@ -66,7 +66,7 @@ def test_invalid_args(kwargs: dict[str, Any]) -> None: @pytest.mark.parametrize( - "dtype", [pl.Decimal, pl.Decimal(12), pl.Decimal(None, 8), pl.Decimal(6, 2)] + "dtype", [pl.Decimal, pl.Decimal(12), pl.Decimal(None, 0), pl.Decimal(6, 0)] ) def test_any_decimal_dtype_passes(dtype: DataTypeClass) -> None: df = pl.DataFrame(schema={"a": dtype}) @@ -171,3 +171,58 @@ def test_validate_range( actual = evaluate_rules(lf, rules_from_exprs(column.validation_rules(pl.col("a")))) expected = pl.LazyFrame(valid) assert_frame_equal(actual, expected) + + +@pytest.mark.parametrize( + ("schema_precision", "schema_scale", "dtype", "should_pass"), + [ + # Exact match should pass + (38, 10, pl.Decimal(38, 10), True), + # Wrong scale should fail + (38, 10, pl.Decimal(38, 3), False), + # Wrong precision should fail + (10, 2, pl.Decimal(38, 2), False), + # Both wrong should fail + (10, 2, pl.Decimal(38, 5), False), + # precision=None should accept any precision with matching scale + (None, 5, pl.Decimal(10, 5), True), + (None, 5, pl.Decimal(20, 5), True), + # precision=None with wrong scale should fail + (None, 5, pl.Decimal(10, 3), False), + ], +) +def test_precision_scale_validation( + schema_precision: int | None, + schema_scale: int, + dtype: DataTypeClass, + should_pass: bool, +) -> None: + class TestSchema(dy.Schema): + a = dy.Decimal(precision=schema_precision, scale=schema_scale) + + df = pl.DataFrame(schema={"a": dtype}) + assert TestSchema.is_valid(df) == should_pass + + +@pytest.mark.parametrize( + ("schema_precision", "schema_scale", "input_dtype"), + [ + (38, 10, pl.Decimal(38, 3)), + (10, 2, pl.Decimal(38, 2)), + (10, 5, pl.Decimal(20, 3)), + ], +) +def test_precision_scale_casting( + schema_precision: int | None, + schema_scale: int, + input_dtype: DataTypeClass, +) -> None: + class TestSchema(dy.Schema): + a = dy.Decimal(precision=schema_precision, scale=schema_scale) + + df_input = pl.DataFrame({"a": [decimal.Decimal("12.34")]}).with_columns( + pl.col("a").cast(input_dtype) + ) + df_validated = TestSchema.validate(df_input, cast=True) + assert df_validated.schema["a"].precision == schema_precision + assert df_validated.schema["a"].scale == schema_scale From 2f71e75a730a8048c59ee4f35b4bebce82b4defd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:31:27 +0000 Subject: [PATCH 3/5] Fix type annotations for decimal validation Co-authored-by: borchero <22455425+borchero@users.noreply.github.com> --- dataframely/columns/decimal.py | 2 +- tests/column_types/test_decimal.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dataframely/columns/decimal.py b/dataframely/columns/decimal.py index 07db592..bcdf1e9 100644 --- a/dataframely/columns/decimal.py +++ b/dataframely/columns/decimal.py @@ -98,7 +98,7 @@ def dtype(self) -> pl.DataType: return pl.Decimal(self.precision, self.scale) def validate_dtype(self, dtype: PolarsDataType) -> bool: - if not dtype.is_decimal(): + if not isinstance(dtype, pl.Decimal): return False # Scale must always match if dtype.scale != self.scale: diff --git a/tests/column_types/test_decimal.py b/tests/column_types/test_decimal.py index e96b507..aa1d242 100644 --- a/tests/column_types/test_decimal.py +++ b/tests/column_types/test_decimal.py @@ -224,5 +224,5 @@ class TestSchema(dy.Schema): pl.col("a").cast(input_dtype) ) df_validated = TestSchema.validate(df_input, cast=True) - assert df_validated.schema["a"].precision == schema_precision - assert df_validated.schema["a"].scale == schema_scale + assert df_validated.schema["a"].precision == schema_precision # type: ignore[attr-defined] + assert df_validated.schema["a"].scale == schema_scale # type: ignore[attr-defined] From e42dce16e12b1640bbf867c7b39edbf50ce449eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:33:42 +0000 Subject: [PATCH 4/5] Fix type annotation in test based on code review feedback Co-authored-by: borchero <22455425+borchero@users.noreply.github.com> --- tests/column_types/test_decimal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/column_types/test_decimal.py b/tests/column_types/test_decimal.py index aa1d242..35d9c6e 100644 --- a/tests/column_types/test_decimal.py +++ b/tests/column_types/test_decimal.py @@ -213,7 +213,7 @@ class TestSchema(dy.Schema): ], ) def test_precision_scale_casting( - schema_precision: int | None, + schema_precision: int, schema_scale: int, input_dtype: DataTypeClass, ) -> None: From 6dbfd080c092d36bae2a83ec5a4a45b4bc5ff519 Mon Sep 17 00:00:00 2001 From: Oliver Borchert Date: Fri, 12 Dec 2025 18:36:53 +0100 Subject: [PATCH 5/5] Simplify implementation --- dataframely/columns/decimal.py | 14 +++++--------- tests/column_types/test_decimal.py | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/dataframely/columns/decimal.py b/dataframely/columns/decimal.py index bcdf1e9..7d5d719 100644 --- a/dataframely/columns/decimal.py +++ b/dataframely/columns/decimal.py @@ -98,15 +98,11 @@ def dtype(self) -> pl.DataType: return pl.Decimal(self.precision, self.scale) def validate_dtype(self, dtype: PolarsDataType) -> bool: - if not isinstance(dtype, pl.Decimal): - return False - # Scale must always match - if dtype.scale != self.scale: - return False - # Precision must match if explicitly specified in the schema - if self.precision is not None and dtype.precision != self.precision: - return False - return True + return ( + isinstance(dtype, pl.Decimal) + and dtype.scale == self.scale + and (self.precision is None or dtype.precision == self.precision) + ) def sqlalchemy_dtype(self, dialect: sa.Dialect) -> sa_TypeEngine: if self.scale and not self.precision: diff --git a/tests/column_types/test_decimal.py b/tests/column_types/test_decimal.py index 35d9c6e..ec4dc03 100644 --- a/tests/column_types/test_decimal.py +++ b/tests/column_types/test_decimal.py @@ -215,7 +215,7 @@ class TestSchema(dy.Schema): def test_precision_scale_casting( schema_precision: int, schema_scale: int, - input_dtype: DataTypeClass, + input_dtype: pl.DataType, ) -> None: class TestSchema(dy.Schema): a = dy.Decimal(precision=schema_precision, scale=schema_scale)