-
-
Notifications
You must be signed in to change notification settings - Fork 133
Description
This is the same as #648, except I'm using the tagged union strategy. test_parents_with_generics fails with this strategy:
> cattrs.strategies.include_subclasses(GenericParent[str], converter, union_strategy=cattrs.strategies.configure_tagged_union)
[snip]
E AttributeError: __subclasses__. Did you mean: '__subclasscheck__'?
The problem is that _include_subclasses_with_union_strategy -> _has_subclasses still calls cl.__subclasses__(), and typing.get_origin(cl) needs to be used here too.
Fixing _has_subclasses leads to a further error:
File "test_cattrutil.py", line 111, in test_test
cattrs.strategies.include_subclasses(GenericParent[str], converter, union_strategy=cattrs.strategies.configure_tagged_union)
File "cattrs/strategies/_subclasses.py", line 84, in include_subclasses
_include_subclasses_with_union_strategy(
File "cattrs/strategies/_subclasses.py", line 234, in _include_subclasses_with_union_strategy
subclasses = tuple([c for c in union_classes if issubclass(c, cl)])
^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/typing.py", line 1225, in __subclasscheck__
raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
Changing this to read subclasses = tuple([c for c in union_classes if issubclass(typing.get_origin(c) or c, typing.get_origin(cl) or cl)]) works. This extended test succeeds - note that there is no reason to specify Parent[str] anymore; we don't have to know what type arguments Parent can ever get, as long as every child class has no open type parameters left:
converter = cattrs.Converter()
@define
class GenericParent[T]:
p: T
@define
class Child1G(GenericParent[str]):
c: str
@define
class Child2G(GenericParent[int]):
c: str
cattrs.strategies.include_subclasses(GenericParent[Any], converter, union_strategy=cattrs.strategies.configure_tagged_union)
assert converter.structure(converter.unstructure(Child1G('a', 'b')), GenericParent[Any]) == Child1G('a', 'b')
assert converter.structure(converter.unstructure(Child2G(1, 'b')), GenericParent[Any]) == Child2G(1, 'b')
I will submit this as a PR, but I'm not sure about the limitations of this code (intended or otherwise), there may well be reasons not to merge it as is.