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

Structure and unstructure generic classes #65

Closed
zhukovgreen opened this issue Aug 21, 2019 · 3 comments
Closed

Structure and unstructure generic classes #65

zhukovgreen opened this issue Aug 21, 2019 · 3 comments

Comments

@zhukovgreen
Copy link

It is a feature request.

it would be nice if cattrs would support Generic classes definition, i.e.

T = TypeVar("T")

@attr.dataclass
class A(Generic[T]):
    a: T = attr.ib()

@attr.dataclass
class B:
    a_inst: A[int] = attr.ib()
@zhukovgreen
Copy link
Author

zhukovgreen commented Aug 23, 2019

Now doing something like this:

# cattrs structure hook
def generic_cls_structure(val, cls):
    """Deserialize the object into generic class.

    Cattrs is not working with generic classes yet.
    https://github.com/Tinche/cattrs/issues/65

    However, in order to be able to deserialize such classes
    the solution assumes the replacing of the attributes with
    generic class (TypeVar) with the subscritped type.

    Current limitations:
    works only with List[~T], Tuple[~T] or ~T
    """
    origin_cls = cls.__origin__
    subscrited_cls = cls.__args__[0]
    new_origin_cls_attribs = {}
    t_map = {list: typing.List, tuple: typing.Tuple}
    for f_name, attrib in attr.fields_dict(origin_cls).items():
        f_name: str
        attrib: attr.Attribute
        _counting_attr_props = dict(
            default=attrib.default,
            validator=attrib.validator,
            repr=attrib.repr,
            cmp=attrib.cmp,
            hash=attrib.hash,
            init=attrib.init,
            converter=attrib.converter,
            metadata=attrib.metadata,
            type=attrib.type,
            kw_only=attrib.kw_only,
        )
        # case with List[~T] or Tuple[~T]
        if (
            hasattr(attrib.type, "__origin__")
            and type(attrib.type.__args__[0]) is typing.TypeVar
        ):
            _counting_attr_props["type"] = t_map[attrib.type.__origin__][
                subscrited_cls
            ]
        # case with ~T
        elif type(attrib.type) is typing.TypeVar:
            _counting_attr_props["type"] = subscrited_cls

        new_origin_cls_attribs[f_name] = _CountingAttr(**_counting_attr_props)

    new_origin_cls = attr.make_class(
        name=origin_cls.__name__, attrs=new_origin_cls_attribs
    )
    return cattr.structure(val, new_origin_cls)

@wakemaster39
Copy link

if you can handle a fork, #51 I have been using very heavily now in my production environment. It changes the signature of hooks is the only side effect from 2 vars to 3 but it supports deeply nested generics.

@Tinche
Copy link
Member

Tinche commented Mar 21, 2021

@zhukovgreen Fixed on master!

@Tinche Tinche closed this as completed Mar 21, 2021
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

3 participants