cattrs icon indicating copy to clipboard operation
cattrs copied to clipboard

[regression][1.3] Unstructure as dict ignores dynamic type

Open RomainMuller opened this issue 4 years ago • 4 comments

  • cattrs version: 1.3.0
  • Python version: 3.7+ (mostly because I use cattrs~=1.0.0 for python_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.

RomainMuller avatar Feb 09 '21 13:02 RomainMuller

Are you trying to unstructure a type annotated attrs class? Can you just annotate the field as enum.Enum instead of Any?

Tinche avatar Feb 09 '21 16:02 Tinche

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.

RomainMuller avatar Mar 02 '21 08:03 RomainMuller

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 avatar Mar 02 '21 09:03 RomainMuller

@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.

Tinche avatar Mar 02 '21 12:03 Tinche

It's been a while, going to close this as stale. Feel free to open a new one if you want to continue!

Tinche avatar Nov 19 '23 22:11 Tinche