diff --git a/HISTORY.md b/HISTORY.md index b7725df7..44aef2f7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -21,6 +21,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python ([#696](https://github.com/python-attrs/cattrs/pull/696)) - Use the optional `_value_` type hint to structure and unstructure enums if present. ([#699](https://github.com/python-attrs/cattrs/issues/699)) +- Aliases (when in use) now properly generate rename metadata in generated hooks. + ([#706](https://github.com/python-attrs/cattrs/issues/706) [#710](https://github.com/python-attrs/cattrs/pull/710)) - _cattrs_ now tracks performance using [codspeed](https://codspeed.io/python-attrs/cattrs). ([#703](https://github.com/python-attrs/cattrs/pull/703)) - The {mod}`tomlkit ` preconf converter now properly handles native `date` objects when structuring. diff --git a/src/cattrs/gen/__init__.py b/src/cattrs/gen/__init__.py index 3357aaca..792e7bf5 100644 --- a/src/cattrs/gen/__init__.py +++ b/src/cattrs/gen/__init__.py @@ -4,7 +4,7 @@ from collections.abc import Callable, Iterable, Mapping from typing import TYPE_CHECKING, Any, Final, Literal, TypeVar -from attrs import NOTHING, Attribute, Converter, Factory +from attrs import NOTHING, Attribute, Converter, Factory, evolve from typing_extensions import NoDefault from .._compat import ( @@ -124,6 +124,8 @@ def make_dict_unstructure_fn_from_attrs( continue if override.rename is None: kn = attr_name if not _cattrs_use_alias else a.alias + if kn != attr_name: + kwargs[attr_name] = evolve(override, rename=kn) else: kn = override.rename d = a.default @@ -435,6 +437,8 @@ def make_dict_structure_fn_from_attrs( ian = a.alias if override.rename is None: kn = an if not _cattrs_use_alias else a.alias + if kn != an: + kwargs[an] = evolve(override, rename=kn) else: kn = override.rename @@ -562,6 +566,8 @@ def make_dict_structure_fn_from_attrs( if override.rename is None: kn = an if not _cattrs_use_alias else a.alias + if kn != an: + kwargs[an] = evolve(override, rename=kn) else: kn = override.rename allowed_fields.add(kn) @@ -631,6 +637,8 @@ def make_dict_structure_fn_from_attrs( if override.rename is None: kn = an if not _cattrs_use_alias else a.alias + if kn != an: + kwargs[an] = evolve(override, rename=kn) else: kn = override.rename allowed_fields.add(kn) diff --git a/tests/test_gen_dict.py b/tests/test_gen_dict.py index 84cd6258..f282ddb2 100644 --- a/tests/test_gen_dict.py +++ b/tests/test_gen_dict.py @@ -419,6 +419,30 @@ class A: ) +def test_alias_implicit_rename(converter: BaseConverter) -> None: + """Attributes with aliases generate rename metadata.""" + + @define + class A: + a: int = field(alias="b") + c: int = field(alias="d", default=1) + + u_fn = make_dict_unstructure_fn(A, converter, _cattrs_use_alias=True) + s_fn = make_dict_structure_fn(A, converter, _cattrs_use_alias=True) + + assert u_fn.overrides["a"].rename == "b" + assert s_fn.overrides["a"].rename == "b" + assert u_fn.overrides["c"].rename == "d" + assert s_fn.overrides["c"].rename == "d" + + converter.register_unstructure_hook(A, u_fn) + converter.register_structure_hook(A, s_fn) + + assert converter.unstructure(A(1)) == {"b": 1, "d": 1} + assert converter.structure({"b": 1}, A) == A(1) + assert converter.structure({"b": 1, "d": 2}, A) == A(1, 2) + + def test_init_false(converter: BaseConverter) -> None: """By default init=False keys are ignored."""