Skip to content

cattr.structure doesn't support generic parameters. #149

@asford

Description

@asford

Partially a note to myself, if you're open to a PR fixing this.

cattr has solid support for generics in unstructure/structure operations, however it doesn't fully support nested generic types.

Consider instances where a generic attrs class contains a generic collection as a member...

import attr
from typing import Generic, TypeVar

T = TypeVar("T")

@attr.define
class Foo(Generic[T]):
   bar: Dict[str, T]

cattr.structure({"bar" : {"bat": 1}}, Foo[int])

...this fails, though one might expect cattr to propagate the T typevar into the Dict[str, T] generic member for an inferred Dict[str, int] field type.

This can probably be fixed by mildly extending the existing gen converter logic.

All the TypeVars in the current context are already resolved into mapping at:
https://github.com/Tinche/cattrs/blob/71a7ed2b4c72558af77487ae991f19cc5401c378/src/cattr/gen.py#L117-L126

which is then used to map TypeVars to their concrete type in the generated converter at:
https://github.com/Tinche/cattrs/blob/71a7ed2b4c72558af77487ae991f19cc5401c378/src/cattr/gen.py#L155-L161

Here, generic types could be detected via typing.get_args or the equivalent _compat function.
If the attribute type contains any TypeVar args, these could be replaced via mapping lookups and an updated type created with type.copy_with:

for a in attrs:
    an = a.name
    override = kwargs.pop(an, _neutral)
    type = a.type
    type_args = typing.get_args(type)
    
    if isinstance(type, TypeVar):
        type = getattr(mapping, type.__name__, type)
    elif type_args is not None:
        type_args = [
            getattr(mapping, arg.__name__, arg)
            for ta in type_args
        ]
        type = type.copy_with(
            tuple(
                getattr(mapping, arg.__name__, arg)
                for ta in type_args
            )
        )

This will probably be fragile to updates in the typing module, so should probably be moved into _compat.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions