cattrs
cattrs copied to clipboard
Custom unstructure function is not called when a union type is wrapped in generic types multiple times
- cattrs version: 22.2.0
- Python version: 3.9
- Operating System: Ubuntu 20.04
Description
When you wrap a union type multiple times, e.g., Optional[list[Union[A, B]]]
, the custom unstructure function on the union type is not called. I believe this is related to https://github.com/python-attrs/cattrs/issues/129.
What I Did
import attrs
import cattrs
from typing import Optional, Union
c = cattrs.Converter()
@attrs.define
class A:
a: int
@attrs.define
class B:
a: int
@attrs.define
class C:
f: Optional[list[Union[A, B]]]
c.register_unstructure_hook(
Union[A, B],
lambda o: {"_type": o.__class__.__name__, **c.unstructure(o)},
)
c.register_structure_hook(
Union[A, B], lambda o, t: c.structure(o, A if o["_type"] == "A" else B)
)
inst = C([A(1), B(2)])
unstructured = c.unstructure(inst)
assert unstructured["f"][0]["_type"] == "A"
assert unstructured["f"][1]["_type"] == "B"
assert c.structure(unstructured, C) == inst
You should get the following error:
❯ python reproduce.py
Traceback (most recent call last):
File "/home/peter/programming/seagull/reproduce.py", line 34, in <module>
assert unstructured["f"][0]["_type"] == "A"
KeyError: '_type'
Hm, you're right. The unstructure path for unions is too simple, so it ends up being equivalent to just list
, instead of list[A|B]
. We should fix this.
I think fixing this for arbitrary unions would be too hard (imagine a list[A|B] | list[A|C]
, it'd be too complex to determine which is it), but optionals (which are unions with None
) should be high value and easy enough to detect and support.
@Tinche just to confirm, you mean None | list[A|B]
should be fixed right?
Yep!