cattrs
cattrs copied to clipboard
[regression][1.3] Unstructure as dict ignores dynamic type
- cattrs version: 1.3.0
- Python version: 3.7+ (mostly because I use
cattrs~=1.0.0
forpython_version < 3.7
) - Operating System: macOS, Linux, Windows
Description
I have a custom unstructure hook registered for enum.Enum
as:
self._serializer.register_unstructure_hook(enum.Enum, _unstructure_enum)
However starting at cattrs
1.3, the _unstructure_enum
does not always get called.
In particular at the following line:
https://github.com/Tinche/cattrs/blob/d5e0cf8054b8e7bca31acf0301aad06ea2892754/src/cattr/converters.py#L210
If a.type
is typing.Any
, then _unstructure_enum
is never going to be called because the identity
unstructure function always takes precedence (as it comes from the single dispatch set).
This was changed in 53fd2ade2e052fb1f66a0e22edcee8a2974f1881, and I have thus far been unable to find a way around this issue, meaning cattrs
1.3 is unusable in my application at this point.
Are you trying to unstructure a type annotated attrs class? Can you just annotate the field as enum.Enum
instead of Any
?
Hey @Tinche - sorry for taking so long to get back to you!
The unstructured type looks like so:
@attr.s(auto_attribs=True, frozen=True, slots=True)
class SetRequest:
objref: ObjRef
property: str
value: Any
This corresponds to a wire request to set a property in the jsii
RPC protocol. The value
field here is where the enum value is placed. It corresponds to the value being set on some object's property, and as such, it is typed Any
since it could be "any" value. I could possibly make this type generic, but this would involve a relatively extensive refactor.
Investigating further on this problem, it appears the value
field here cas actually unstructured BEFORE it is set on the SetRequest
value, and this actually gives me more ways to correctly process it.
I've actually been able to resolve this problem by intercepting & transforming the value before it reaches cattrs
, meaning I no longer have the problem.
I'll leave it up to you to decide if you want to keep this issue open or close it.
@RomainMuller Interesting. I could make the Any
type annotation behave as if there is no annotation present, so it would use the actual type of the value. There might be value in doing this regardless.
However I want to see if your use case can already be supported. Why can't you just type the field as enum.Enum
? This works:
from enum import Enum
import attr
import cattr
class TestEnum(Enum):
TEST_MEMBER = "test"
@attr.define
class TestClass:
enum_member: TestEnum
@attr.define
class TestClassEnum:
enum_member: Enum
c = cattr.Converter()
c.register_unstructure_hook(Enum, lambda _: "MAGIC VALUE")
print(c.unstructure(TestClass(TestEnum.TEST_MEMBER)))
print(c.unstructure(TestClassEnum(TestEnum.TEST_MEMBER)))
In the example I've typed the field two ways, once as the actual enum, and then as just an enum.Enum
, and the hooks are called correctly.
It's been a while, going to close this as stale. Feel free to open a new one if you want to continue!