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

Hooking to determine type #140

Closed
jpsnyder opened this issue Mar 26, 2021 · 4 comments
Closed

Hooking to determine type #140

jpsnyder opened this issue Mar 26, 2021 · 4 comments

Comments

@jpsnyder
Copy link

jpsnyder commented Mar 26, 2021

  • cattrs version: 1.4.0
  • Python version: 3.7.5
  • Operating System: Windows

Description

Is there a way to create a special hook that determines the class/type to use based on the results of a function. Similar to register_structure_hook_func(), but for the value?
This would help to support the common technique of including a _type in the dict to determine the correct class within a base class.

As well, it would allow one to modify the value before being structured.

class Element:
    """Some generic base class"""
    ...

    @classmethod
    def _get_class(cls, value: dict):
        try:
            class_name = value["_type"]
            del value["_type"]
        except KeyError:
            return cls
            
        for klass in Element.__subclasses__():
            if klass.__name__ == class_name:
                return klass
        return cls
   

cattr.register_structure_type_hook(Element._get_class, base=Element)
@Tinche
Copy link
Member

Tinche commented Mar 26, 2021

Here's how you can do this now:

from attr import define

from cattr import GenConverter

c = GenConverter()


@define
class Base:
    a: int


@define
class SubOne(Base):
    b: int


@define
class SubTwo(Base):
    c: int


def unstructure_base(b):
    return {"_type": b.__class__.__name__, **c.unstructure_attrs_asdict(b)}


c.register_unstructure_hook(Base, unstructure_base)


print(c.unstructure(SubOne(1, 2)))


def structure_base(val, _):
    sub_name = val.pop("_type")
    for klass in Base.__subclasses__():
        if klass.__name__ == sub_name:
            return c.structure_attrs_fromdict(val, klass)
    return c.structure_attrs_fromdict(val, Base)


c.register_structure_hook(Base, structure_base)

print(c.structure(c.unstructure(SubOne(1, 2)), Base))
print(c.structure(c.unstructure(SubTwo(1, 2)), Base))
print(c.structure(c.unstructure(Base(1)), Base))

A thing to keep in mind: structure and unstructure hooks use functools.singledispatch under the hood, so a hook registered for Base will apply to SubOne and SubTwo, unless there are hooks for those classes registered explicitly.

@jpsnyder
Copy link
Author

Ah thank you! The missing piece of the puzzle for me was the usage of unstructure_attrs_asdict() and structure_attrs_fromdict()
I was trying to use the regular unstructure() and structure() functions within the hook which caused recursion errors. I'm sorry if I missed that part of the documentation.

@Tinche
Copy link
Member

Tinche commented Apr 2, 2021

@jpsnyder ok to close this?

@jpsnyder
Copy link
Author

jpsnyder commented Apr 3, 2021

Yes

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

2 participants