-
-
Notifications
You must be signed in to change notification settings - Fork 133
Description
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.