python-attrs / cattrs

Composable custom class converters for attrs, dataclasses and friends.
https://catt.rs
MIT License
791 stars 110 forks source link

Still not able to structure using an alias #553

Closed cj4ssi closed 1 month ago

cj4ssi commented 1 month ago

Description

It is still not possible to structure a dict using the alias of a field. Supposedly this was fixed in python-attrs/cattrs#322.

Example:

from attrs import mutable, field
from cattrs import stucture

@mutable
class Thing:
  one:int
  _two:int = field(alias='two')

structure({'one': 1, 'two': 2}, Thing)  # raises cattrs.errors.ClassValidationError on "_two" missing from `Thing`

This is due to the line obtaining the value of the to-be-structured data via name only, in `BaseConverter.structure_attrs_fromdict:

for a in fields(cl):
    try:
        val = obj[a.name]
    except KeyError:
        continue

https://github.com/python-attrs/cattrs/blob/6290cacdb7f9d195b4f96ce0ab036c8eebf35d94/src/cattrs/converters.py#L733

The fix is easy, but I am only authorized to file issues, not fixes, with this account. Will submit MR when possible.

Tinche commented 1 month ago

So that's actually working as intended, see https://catt.rs/en/latest/customizing.html#use-alias for more details.

Aliases aren't used by default, that would have been a large compatibility break when it was introduced in attrs.

Here's a snippet to enable them for your class:

from attrs import field, mutable

from cattrs import Converter
from cattrs.gen import make_dict_structure_fn

@mutable
class Thing:
    one: int
    _two: int = field(alias="two")

c = Converter()
c.register_structure_hook(
    Thing, make_dict_structure_fn(Thing, c, _cattrs_use_alias=True)
)

c.structure({"one": 1, "two": 2}, Thing)

They can also be enabled for all attrs classes using a hook factory, let me know if you'd like an example for that.

NodeJSmith commented 1 month ago

PSA: Make sure you call structure on the converter instance (e.g. c.structure, like @Tinche's example), not on cattrs. Otherwise you'll spend 30 minutes trying to figure out what's not working, write up a whole comment explaining the issue and then erase it to write a PSA instead

Tinche commented 1 month ago

I know you're joking but if you apply the register_structure_hook to the global converter (cattrs.global_converter) it'll work with cattrs.structure. I always recommend having a separate converter though, because who knows what else could be using the global one.