Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make_unstructure_dict_unstructure_fn does not honor use_class_methods #566

Open
salotz opened this issue Aug 1, 2024 · 0 comments
Open

Comments

@salotz
Copy link

salotz commented Aug 1, 2024

From comments in #558. Custom metamethods and make_unstructure_dict_unstructure_fn cannot be used together:

import cattrs
from cattrs.strategies import use_class_methods
import attrs

# Use a set of metamethods
@attrs.define
class Thing:
    a: int

    def _unstructure(self):
        return {"a" : str(self.a)}

    @classmethod
    def _structure(cls, val):
        return cls(a=int(val["a"]))

conv = cattrs.Converter()
use_class_methods(
    conv,
    "_structure",
    "_unstructure",
)

assert conv.unstructure(Thing(1)) == {
    "a" : "1",
}


def tag_attrs_hook_factory(cl):

    base_hook = cattrs.gen.make_dict_unstructure_fn(cl, conv)

    def hook(instance):

        unstruct = base_hook(instance)
        unstruct["_type"] = type(instance).__name__

        return unstruct
    return hook


tagging_conv_a = cattrs.Converter()
tagging_conv_a.register_unstructure_hook_factory(attrs.has, tag_attrs_hook_factory)
use_class_methods(
    tagging_conv_a,
    "_structure",
    "_unstructure",
)

# THIS IS WRONG. Does not have the tag
assert tagging_conv_a.unstructure(Thing(1)) == {
    "a" : "1",
}

tagging_conv_b = cattrs.Converter()
use_class_methods(
    tagging_conv_b,
    "_structure",
    "_unstructure",
)
tagging_conv_b.register_unstructure_hook_factory(attrs.has, tag_attrs_hook_factory)

# THIS IS WRONG. Does not convert the sub value correctly via the metamethod
assert tagging_conv_b.unstructure(Thing(1)) == {
    "_type" : "Thing",
    "a" : 1,
}

What I tried initially was to just inject the converter so it dispatches to the metamethods properly. But this causes infinite recursion, e.g.:

def tag_attrs_hook_factory(cl):

    converter = ... # via closure

    def hook(instance, converter):

        unstruct = converter.unstructure(instance)
        unstruct["_type"] = type(instance).__name__

        return unstruct
    return hook

I think that make_dict_unstructure_fn and friends should honor the metamethods for the passed in converter and break recursion. I'm not sure if this makes sense though... In any case this is a difficult case to handle generally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant