From 036c53589025469d882227d0bf7be764ed8646a7 Mon Sep 17 00:00:00 2001 From: gabriel Date: Tue, 3 Mar 2026 23:54:21 +0100 Subject: [PATCH 1/8] undo aliases --- dataframely/_base_schema.py | 55 ++++++++++++++++++++++++++++--- dataframely/schema.py | 26 +++++++++++++++ tests/columns/test_alias.py | 66 +++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 5 deletions(-) diff --git a/dataframely/_base_schema.py b/dataframely/_base_schema.py index 7ae64e3..c55916a 100644 --- a/dataframely/_base_schema.py +++ b/dataframely/_base_schema.py @@ -24,6 +24,8 @@ _COLUMN_ATTR = "__dataframely_columns__" _RULE_ATTR = "__dataframely_rules__" +_ATTR_TO_ALIAS = "__dataframely_attr_to_alias__" +_USE_ATTR_NAMES = "__dataframely_use_attribute_names__" ORIGINAL_COLUMN_PREFIX = "__DATAFRAMELY_ORIGINAL__" @@ -82,10 +84,12 @@ class Metadata: columns: dict[str, Column] = field(default_factory=dict) rules: dict[str, RuleFactory] = field(default_factory=dict) + attr_to_alias: dict[str, str | None] = field(default_factory=dict) def update(self, other: Self) -> None: self.columns.update(other.columns) self.rules.update(other.rules) + self.attr_to_alias.update(other.attr_to_alias) class SchemaMeta(ABCMeta): @@ -95,13 +99,31 @@ def __new__( bases: tuple[type[object], ...], namespace: dict[str, Any], *args: Any, + use_attribute_names: bool | None = None, **kwargs: Any, ) -> SchemaMeta: result = Metadata() + + # Inherit use_attribute_names from parent if not explicitly set + inherited_use_attr_names = False for base in bases: result.update(mcs._get_metadata_recursively(base)) - result.update(mcs._get_metadata(namespace)) + if hasattr(base, _USE_ATTR_NAMES): + inherited_use_attr_names = getattr(base, _USE_ATTR_NAMES) + + # Explicit setting takes precedence over inheritance + final_use_attr_names = ( + use_attribute_names + if use_attribute_names is not None + else inherited_use_attr_names + ) + + result.update( + mcs._get_metadata(namespace, use_attribute_names=final_use_attr_names) + ) namespace[_COLUMN_ATTR] = result.columns + namespace[_ATTR_TO_ALIAS] = result.attr_to_alias + namespace[_USE_ATTR_NAMES] = final_use_attr_names cls = super().__new__(mcs, name, bases, namespace, *args, **kwargs) # Assign rules retroactively as we only encounter rule factories in the result @@ -185,7 +207,8 @@ def __getattribute__(cls, name: str) -> Any: val = super().__getattribute__(name) # Dynamically set the name of the column if it is a `Column` instance. if isinstance(val, Column): - val._name = val.alias or name + use_attr_names = getattr(cls, _USE_ATTR_NAMES, False) + val._name = name if use_attr_names else (val.alias or name) return val @staticmethod @@ -193,17 +216,25 @@ def _get_metadata_recursively(kls: type[object]) -> Metadata: result = Metadata() for base in kls.__bases__: result.update(SchemaMeta._get_metadata_recursively(base)) - result.update(SchemaMeta._get_metadata(kls.__dict__)) # type: ignore + use_attr_names = getattr(kls, _USE_ATTR_NAMES, False) + result.update( + SchemaMeta._get_metadata(kls.__dict__, use_attribute_names=use_attr_names) # type: ignore + ) return result @staticmethod - def _get_metadata(source: dict[str, Any]) -> Metadata: + def _get_metadata( + source: dict[str, Any], *, use_attribute_names: bool = False + ) -> Metadata: result = Metadata() for attr, value in { k: v for k, v in source.items() if not k.startswith("__") }.items(): if isinstance(value, Column): - result.columns[value.alias or attr] = value + # When use_attribute_names=True, use attr as key; otherwise use alias or attr + col_name = attr if use_attribute_names else (value.alias or attr) + result.columns[col_name] = value + result.attr_to_alias[attr] = value.alias if isinstance(value, RuleFactory): # We must ensure that custom rules do not clash with internal rules. if attr == "primary_key": @@ -258,3 +289,17 @@ def _validation_rules(cls, *, with_cast: bool) -> dict[str, Rule]: @classmethod def _schema_validation_rules(cls) -> dict[str, Rule]: return getattr(cls, _RULE_ATTR) + + @classmethod + def _alias_mapping(cls) -> dict[str, str]: + """Mapping from aliases to column identifier (attribute).""" + return { + alias: attr + for attr, alias in getattr(cls, _ATTR_TO_ALIAS).items() + if alias is not None and alias != attr + } + + @classmethod + def _uses_attribute_names(cls) -> bool: + """Check if the schema uses attribute names instead of aliases.""" + return getattr(cls, _USE_ATTR_NAMES, False) diff --git a/dataframely/schema.py b/dataframely/schema.py index b64f061..04a7385 100644 --- a/dataframely/schema.py +++ b/dataframely/schema.py @@ -820,6 +820,32 @@ def cast( return lf.collect() # type: ignore return lf # type: ignore + @overload + @classmethod + def undo_aliases(cls, df: pl.DataFrame, /) -> pl.DataFrame: ... + + @overload + @classmethod + def undo_aliases(cls, df: pl.LazyFrame, /) -> pl.LazyFrame: ... + + @classmethod + def undo_aliases( + cls, df: pl.DataFrame | pl.LazyFrame, / + ) -> pl.DataFrame | pl.LazyFrame: + """Rename columns from their alias names to their attribute names. + + This method renames columns that have aliases defined, mapping from the + alias (e.g., "price ($)") to the attribute name (e.g., "price"). + + Args: + df: The data frame whose columns should be renamed. + + Returns: + The data frame with columns renamed from aliases to attribute names. + Columns without aliases are left unchanged. + """ + return df.rename(cls._alias_mapping()) + # --------------------------------- SERIALIZATION -------------------------------- # @classmethod diff --git a/tests/columns/test_alias.py b/tests/columns/test_alias.py index 0dd364a..034c48c 100644 --- a/tests/columns/test_alias.py +++ b/tests/columns/test_alias.py @@ -36,3 +36,69 @@ def test_alias_unset() -> None: no_alias_col = dy.Int32() assert no_alias_col.alias is None assert no_alias_col.name == "" + + +def test_alias_use_attribute_names() -> None: + class MySchema1(dy.Schema, use_attribute_names=True): + price = dy.Int64(alias="price ($)") + + class MySchema2(MySchema1, use_attribute_names=False): + price2 = dy.Int64(alias="price2 ($)") + + class MySchema3(MySchema2): + price3 = dy.Int64(alias="price3 ($)") + + class MySchema4(MySchema3, use_attribute_names=True): + price4 = dy.Int64(alias="price4 ($)") + + class MySchema5(MySchema4): + price5 = dy.Int64(alias="price5 ($)") + + assert MySchema5.column_names() == [ + "price", + "price2 ($)", + "price3 ($)", + "price4", + "price5", + ] + + +def test_alias_mapping() -> None: + class MySchema(dy.Schema): + price = dy.Int64(alias="price ($)") + production_rank = dy.Int64(alias="Production rank") + no_alias = dy.Int64() + + # _alias_mapping returns alias -> attribute name mapping + assert MySchema._alias_mapping() == { + "price ($)": "price", + "Production rank": "production_rank", + } + + +def test_alias_mapping_empty() -> None: + class NoAliasSchema(dy.Schema): + a = dy.Int64() + b = dy.String() + + # No aliases means empty mapping + assert NoAliasSchema._alias_mapping() == {} + + +def test_undo_aliases() -> None: + class MySchema(dy.Schema): + price = dy.Int64(alias="price ($)") + production_rank = dy.Int64(alias="Production rank") + + df = pl.DataFrame({"price ($)": [100], "Production rank": [1]}) + result = MySchema.undo_aliases(df) + assert result.columns == ["price", "production_rank"] + + +def test_undo_aliases_lazy() -> None: + class MySchema(dy.Schema): + price = dy.Int64(alias="price ($)") + + lf = pl.LazyFrame({"price ($)": [100]}) + result = MySchema.undo_aliases(lf).collect() + assert result.columns == ["price"] From 242a8cfd4bf4cdabcce728a1322d2d5b9cc54dfa Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 00:55:12 +0100 Subject: [PATCH 2/8] inherit restriction --- dataframely/_base_schema.py | 21 ++++++++++++++++++--- tests/columns/test_alias.py | 30 +++++++++++++++--------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/dataframely/_base_schema.py b/dataframely/_base_schema.py index c55916a..3750ac4 100644 --- a/dataframely/_base_schema.py +++ b/dataframely/_base_schema.py @@ -104,14 +104,29 @@ def __new__( ) -> SchemaMeta: result = Metadata() - # Inherit use_attribute_names from parent if not explicitly set - inherited_use_attr_names = False + # Inherit use_attribute_names from parent + inherited_use_attr_names: bool = False for base in bases: result.update(mcs._get_metadata_recursively(base)) if hasattr(base, _USE_ATTR_NAMES): inherited_use_attr_names = getattr(base, _USE_ATTR_NAMES) - # Explicit setting takes precedence over inheritance + # Disallow changing use_attribute_names in subclasses when bases already + # define a different value. Mixed-name schemas would make column + # metadata inconsistent with attribute access and validation. + # Only check if base has actual columns (ignore root Schema class). + if ( + use_attribute_names is not None + and use_attribute_names != inherited_use_attr_names + and len(result.columns) > 0 + ): + raise ImplementationError( + f"Cannot override 'use_attribute_names' in subclass '{name}': " + f"base schema uses use_attribute_names={inherited_use_attr_names!r}, " + f"but subclass requested use_attribute_names={use_attribute_names!r}." + ) + + # Use explicit value if provided, otherwise inherit from parent final_use_attr_names = ( use_attribute_names if use_attribute_names is not None diff --git a/tests/columns/test_alias.py b/tests/columns/test_alias.py index 034c48c..3e5746a 100644 --- a/tests/columns/test_alias.py +++ b/tests/columns/test_alias.py @@ -2,8 +2,10 @@ # SPDX-License-Identifier: BSD-3-Clause import polars as pl +import pytest import dataframely as dy +from dataframely.exc import ImplementationError class AliasSchema(dy.Schema): @@ -38,29 +40,27 @@ def test_alias_unset() -> None: assert no_alias_col.name == "" -def test_alias_use_attribute_names() -> None: +def test_alias_use_attribute_names_inherited() -> None: class MySchema1(dy.Schema, use_attribute_names=True): price = dy.Int64(alias="price ($)") - class MySchema2(MySchema1, use_attribute_names=False): + class MySchema2(MySchema1): price2 = dy.Int64(alias="price2 ($)") - class MySchema3(MySchema2): - price3 = dy.Int64(alias="price3 ($)") + # Inheritance: use_attribute_names=True is inherited automatically + assert MySchema2.column_names() == ["price", "price2"] - class MySchema4(MySchema3, use_attribute_names=True): - price4 = dy.Int64(alias="price4 ($)") - class MySchema5(MySchema4): - price5 = dy.Int64(alias="price5 ($)") +def test_alias_use_attribute_names_override_disallowed() -> None: + class MySchema1(dy.Schema, use_attribute_names=True): + price = dy.Int64(alias="price ($)") + + with pytest.raises( + ImplementationError, match="Cannot override 'use_attribute_names'" + ): - assert MySchema5.column_names() == [ - "price", - "price2 ($)", - "price3 ($)", - "price4", - "price5", - ] + class MySchema2(MySchema1, use_attribute_names=False): + price2 = dy.Int64(alias="price2 ($)") def test_alias_mapping() -> None: From 67c0d625ec3acd74352326ff572ece8e7f674e84 Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 09:41:19 +0100 Subject: [PATCH 3/8] Revert "inherit restriction" This reverts commit 242a8cfd4bf4cdabcce728a1322d2d5b9cc54dfa. --- dataframely/_base_schema.py | 21 +++------------------ tests/columns/test_alias.py | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/dataframely/_base_schema.py b/dataframely/_base_schema.py index 3750ac4..c55916a 100644 --- a/dataframely/_base_schema.py +++ b/dataframely/_base_schema.py @@ -104,29 +104,14 @@ def __new__( ) -> SchemaMeta: result = Metadata() - # Inherit use_attribute_names from parent - inherited_use_attr_names: bool = False + # Inherit use_attribute_names from parent if not explicitly set + inherited_use_attr_names = False for base in bases: result.update(mcs._get_metadata_recursively(base)) if hasattr(base, _USE_ATTR_NAMES): inherited_use_attr_names = getattr(base, _USE_ATTR_NAMES) - # Disallow changing use_attribute_names in subclasses when bases already - # define a different value. Mixed-name schemas would make column - # metadata inconsistent with attribute access and validation. - # Only check if base has actual columns (ignore root Schema class). - if ( - use_attribute_names is not None - and use_attribute_names != inherited_use_attr_names - and len(result.columns) > 0 - ): - raise ImplementationError( - f"Cannot override 'use_attribute_names' in subclass '{name}': " - f"base schema uses use_attribute_names={inherited_use_attr_names!r}, " - f"but subclass requested use_attribute_names={use_attribute_names!r}." - ) - - # Use explicit value if provided, otherwise inherit from parent + # Explicit setting takes precedence over inheritance final_use_attr_names = ( use_attribute_names if use_attribute_names is not None diff --git a/tests/columns/test_alias.py b/tests/columns/test_alias.py index 3e5746a..034c48c 100644 --- a/tests/columns/test_alias.py +++ b/tests/columns/test_alias.py @@ -2,10 +2,8 @@ # SPDX-License-Identifier: BSD-3-Clause import polars as pl -import pytest import dataframely as dy -from dataframely.exc import ImplementationError class AliasSchema(dy.Schema): @@ -40,27 +38,29 @@ def test_alias_unset() -> None: assert no_alias_col.name == "" -def test_alias_use_attribute_names_inherited() -> None: +def test_alias_use_attribute_names() -> None: class MySchema1(dy.Schema, use_attribute_names=True): price = dy.Int64(alias="price ($)") - class MySchema2(MySchema1): + class MySchema2(MySchema1, use_attribute_names=False): price2 = dy.Int64(alias="price2 ($)") - # Inheritance: use_attribute_names=True is inherited automatically - assert MySchema2.column_names() == ["price", "price2"] + class MySchema3(MySchema2): + price3 = dy.Int64(alias="price3 ($)") + class MySchema4(MySchema3, use_attribute_names=True): + price4 = dy.Int64(alias="price4 ($)") -def test_alias_use_attribute_names_override_disallowed() -> None: - class MySchema1(dy.Schema, use_attribute_names=True): - price = dy.Int64(alias="price ($)") - - with pytest.raises( - ImplementationError, match="Cannot override 'use_attribute_names'" - ): + class MySchema5(MySchema4): + price5 = dy.Int64(alias="price5 ($)") - class MySchema2(MySchema1, use_attribute_names=False): - price2 = dy.Int64(alias="price2 ($)") + assert MySchema5.column_names() == [ + "price", + "price2 ($)", + "price3 ($)", + "price4", + "price5", + ] def test_alias_mapping() -> None: From a065a97a15a3c03ec499d5ad46a3e399d01e088e Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 10:59:13 +0100 Subject: [PATCH 4/8] allow inheritage: set _name at init --- dataframely/_base_schema.py | 39 ++++++++-------------- tests/columns/test_alias.py | 66 +++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/dataframely/_base_schema.py b/dataframely/_base_schema.py index c55916a..5eeef42 100644 --- a/dataframely/_base_schema.py +++ b/dataframely/_base_schema.py @@ -8,7 +8,7 @@ from abc import ABCMeta from copy import copy from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any +from typing import Any import polars as pl @@ -24,7 +24,6 @@ _COLUMN_ATTR = "__dataframely_columns__" _RULE_ATTR = "__dataframely_rules__" -_ATTR_TO_ALIAS = "__dataframely_attr_to_alias__" _USE_ATTR_NAMES = "__dataframely_use_attribute_names__" ORIGINAL_COLUMN_PREFIX = "__DATAFRAMELY_ORIGINAL__" @@ -84,12 +83,10 @@ class Metadata: columns: dict[str, Column] = field(default_factory=dict) rules: dict[str, RuleFactory] = field(default_factory=dict) - attr_to_alias: dict[str, str | None] = field(default_factory=dict) def update(self, other: Self) -> None: self.columns.update(other.columns) self.rules.update(other.rules) - self.attr_to_alias.update(other.attr_to_alias) class SchemaMeta(ABCMeta): @@ -118,11 +115,18 @@ def __new__( else inherited_use_attr_names ) + # Copy columns defined in current namespace to avoid mutating shared objects. + # Set _name based on this class's use_attribute_names setting. + for attr, value in list(namespace.items()): + if isinstance(value, Column) and not attr.startswith("__"): + col = copy(value) + col._name = attr if final_use_attr_names else (col.alias or attr) + namespace[attr] = col + result.update( mcs._get_metadata(namespace, use_attribute_names=final_use_attr_names) ) namespace[_COLUMN_ATTR] = result.columns - namespace[_ATTR_TO_ALIAS] = result.attr_to_alias namespace[_USE_ATTR_NAMES] = final_use_attr_names cls = super().__new__(mcs, name, bases, namespace, *args, **kwargs) @@ -199,18 +203,6 @@ def __new__( return cls - if not TYPE_CHECKING: - # Only define __getattribute__ at runtime to allow type checkers to properly - # validate attribute access. When TYPE_CHECKING is True, type checkers will use - # the default metaclass behavior which correctly identifies non-existent attributes. - def __getattribute__(cls, name: str) -> Any: - val = super().__getattribute__(name) - # Dynamically set the name of the column if it is a `Column` instance. - if isinstance(val, Column): - use_attr_names = getattr(cls, _USE_ATTR_NAMES, False) - val._name = name if use_attr_names else (val.alias or name) - return val - @staticmethod def _get_metadata_recursively(kls: type[object]) -> Metadata: result = Metadata() @@ -234,7 +226,6 @@ def _get_metadata( # When use_attribute_names=True, use attr as key; otherwise use alias or attr col_name = attr if use_attribute_names else (value.alias or attr) result.columns[col_name] = value - result.attr_to_alias[attr] = value.alias if isinstance(value, RuleFactory): # We must ensure that custom rules do not clash with internal rules. if attr == "primary_key": @@ -269,11 +260,7 @@ def column_names(cls) -> list[str]: @classmethod def columns(cls) -> dict[str, Column]: """The column definitions of this schema.""" - columns: dict[str, Column] = getattr(cls, _COLUMN_ATTR) - for name in columns.keys(): - # Dynamically set the name of the columns. - columns[name]._name = name - return columns + return getattr(cls, _COLUMN_ATTR) @classmethod def primary_key(cls) -> list[str]: @@ -294,9 +281,9 @@ def _schema_validation_rules(cls) -> dict[str, Rule]: def _alias_mapping(cls) -> dict[str, str]: """Mapping from aliases to column identifier (attribute).""" return { - alias: attr - for attr, alias in getattr(cls, _ATTR_TO_ALIAS).items() - if alias is not None and alias != attr + col.alias: col._name + for col in cls.columns().values() + if col.alias is not None and col.alias != col._name } @classmethod diff --git a/tests/columns/test_alias.py b/tests/columns/test_alias.py index 034c48c..d0793ce 100644 --- a/tests/columns/test_alias.py +++ b/tests/columns/test_alias.py @@ -64,7 +64,7 @@ class MySchema5(MySchema4): def test_alias_mapping() -> None: - class MySchema(dy.Schema): + class MySchema(dy.Schema, use_attribute_names=True): price = dy.Int64(alias="price ($)") production_rank = dy.Int64(alias="Production rank") no_alias = dy.Int64() @@ -86,7 +86,7 @@ class NoAliasSchema(dy.Schema): def test_undo_aliases() -> None: - class MySchema(dy.Schema): + class MySchema(dy.Schema, use_attribute_names=True): price = dy.Int64(alias="price ($)") production_rank = dy.Int64(alias="Production rank") @@ -96,9 +96,69 @@ class MySchema(dy.Schema): def test_undo_aliases_lazy() -> None: - class MySchema(dy.Schema): + class MySchema(dy.Schema, use_attribute_names=True): price = dy.Int64(alias="price ($)") lf = pl.LazyFrame({"price ($)": [100]}) result = MySchema.undo_aliases(lf).collect() assert result.columns == ["price"] + + +def test_inherited_column_keeps_parent_name() -> None: + """Inherited columns keep their _name from the parent class.""" + + class Parent(dy.Schema, use_attribute_names=True): + price = dy.Int64(alias="price ($)") + + class Child(Parent, use_attribute_names=False): + quantity = dy.Int64(alias="qty") + + # Parent column keeps its name based on parent's use_attribute_names=True + assert Parent.price.name == "price" + assert Child.price.name == "price" + + # Child's own column uses its use_attribute_names=False setting + assert Child.quantity.name == "qty" + + # column_names reflects the correct names + assert Parent.column_names() == ["price"] + assert Child.column_names() == ["price", "qty"] + + +def test_shared_column_object_is_copied() -> None: + """When a column object is reused, each schema gets its own copy.""" + col = dy.Int64(alias="price ($)") + + class Schema1(dy.Schema, use_attribute_names=True): + price = col + + class Schema2(dy.Schema, use_attribute_names=False): + price = col + + # Each schema has its own copy with the correct _name + assert Schema1.price.name == "price" + assert Schema2.price.name == "price ($)" + + # The original column is not mutated + assert col._name == "" + + +def test_shared_column_in_inheritance() -> None: + """Shared column used in parent and child schemas.""" + col = dy.Int64(alias="price ($)") + + class Parent(dy.Schema, use_attribute_names=True): + price = col + + class Child(Parent, use_attribute_names=False): + price2 = col + + # Parent's column uses parent's setting + assert Parent.price.name == "price" + # Inherited column in child keeps parent's setting + assert Child.price.name == "price" + # Child's own column uses child's setting + assert Child.price2.name == "price ($)" + + assert Parent.column_names() == ["price"] + assert Child.column_names() == ["price", "price ($)"] From f013b1587d355540849d5764434cca50172cd827 Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 11:11:34 +0100 Subject: [PATCH 5/8] don't inherit use_attribute_names --- dataframely/_base_schema.py | 19 ++++--------------- tests/columns/test_alias.py | 2 +- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/dataframely/_base_schema.py b/dataframely/_base_schema.py index 5eeef42..17457c3 100644 --- a/dataframely/_base_schema.py +++ b/dataframely/_base_schema.py @@ -96,38 +96,27 @@ def __new__( bases: tuple[type[object], ...], namespace: dict[str, Any], *args: Any, - use_attribute_names: bool | None = None, + use_attribute_names: bool = False, **kwargs: Any, ) -> SchemaMeta: result = Metadata() - # Inherit use_attribute_names from parent if not explicitly set - inherited_use_attr_names = False for base in bases: result.update(mcs._get_metadata_recursively(base)) - if hasattr(base, _USE_ATTR_NAMES): - inherited_use_attr_names = getattr(base, _USE_ATTR_NAMES) - - # Explicit setting takes precedence over inheritance - final_use_attr_names = ( - use_attribute_names - if use_attribute_names is not None - else inherited_use_attr_names - ) # Copy columns defined in current namespace to avoid mutating shared objects. # Set _name based on this class's use_attribute_names setting. for attr, value in list(namespace.items()): if isinstance(value, Column) and not attr.startswith("__"): col = copy(value) - col._name = attr if final_use_attr_names else (col.alias or attr) + col._name = attr if use_attribute_names else (col.alias or attr) namespace[attr] = col result.update( - mcs._get_metadata(namespace, use_attribute_names=final_use_attr_names) + mcs._get_metadata(namespace, use_attribute_names=use_attribute_names) ) namespace[_COLUMN_ATTR] = result.columns - namespace[_USE_ATTR_NAMES] = final_use_attr_names + namespace[_USE_ATTR_NAMES] = use_attribute_names cls = super().__new__(mcs, name, bases, namespace, *args, **kwargs) # Assign rules retroactively as we only encounter rule factories in the result diff --git a/tests/columns/test_alias.py b/tests/columns/test_alias.py index d0793ce..7cbe40a 100644 --- a/tests/columns/test_alias.py +++ b/tests/columns/test_alias.py @@ -59,7 +59,7 @@ class MySchema5(MySchema4): "price2 ($)", "price3 ($)", "price4", - "price5", + "price5 ($)", ] From bea5c7ce01581cda984b9d80a994ac9cbe3088dc Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 11:14:18 +0100 Subject: [PATCH 6/8] remove unused _uses_attribute_names --- dataframely/_base_schema.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dataframely/_base_schema.py b/dataframely/_base_schema.py index 17457c3..870596f 100644 --- a/dataframely/_base_schema.py +++ b/dataframely/_base_schema.py @@ -274,8 +274,3 @@ def _alias_mapping(cls) -> dict[str, str]: for col in cls.columns().values() if col.alias is not None and col.alias != col._name } - - @classmethod - def _uses_attribute_names(cls) -> bool: - """Check if the schema uses attribute names instead of aliases.""" - return getattr(cls, _USE_ATTR_NAMES, False) From 5bff7774f3e96aecb93e0658bb66d0a5107bae76 Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 11:15:47 +0100 Subject: [PATCH 7/8] rename strict=False --- dataframely/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataframely/schema.py b/dataframely/schema.py index 04a7385..c633719 100644 --- a/dataframely/schema.py +++ b/dataframely/schema.py @@ -844,7 +844,7 @@ def undo_aliases( The data frame with columns renamed from aliases to attribute names. Columns without aliases are left unchanged. """ - return df.rename(cls._alias_mapping()) + return df.rename(cls._alias_mapping(), strict=False) # --------------------------------- SERIALIZATION -------------------------------- # From 52f3b0b3ce18d45ebfc62f28e2c93b6bfa25d73b Mon Sep 17 00:00:00 2001 From: gabriel Date: Wed, 4 Mar 2026 11:20:32 +0100 Subject: [PATCH 8/8] test: copilot suugestion --- tests/columns/test_alias.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/columns/test_alias.py b/tests/columns/test_alias.py index 7cbe40a..9b8d2af 100644 --- a/tests/columns/test_alias.py +++ b/tests/columns/test_alias.py @@ -54,6 +54,12 @@ class MySchema4(MySchema3, use_attribute_names=True): class MySchema5(MySchema4): price5 = dy.Int64(alias="price5 ($)") + assert MySchema5.price.name == "price" + assert MySchema5.price2.name == "price2 ($)" + assert MySchema5.price3.name == "price3 ($)" + assert MySchema5.price4.name == "price4" + assert MySchema5.price5.name == "price5 ($)" + assert MySchema5.column_names() == [ "price", "price2 ($)",