cattrs
cattrs copied to clipboard
Automatic disambiguation fails for Union[BaseClass, SubClass].
- cattrs version: 0.9.0
- Python version: 3.6.9
- Operating System: Red Hat Enterprise Linux Server release 5.3 (Tikanga)
Description
I tried to structure
data for a SubClass
into a typing.Union[BaseClass, SubClass]
, but got a TypeError
because it tried to instantiate a BaseClass when it should have created a SubClass.
The root cause is that when I called structure(data, typing.Union[BaseClass, SubClass])
, it was actually calling structure(data, BaseClass)
, because typing.Union
ignores the subclass -- "When a class and its subclass are present, the latter is skipped". (docs)
The cattr
docs advertise it can structure typing.Union's of supported attrs classes, given that all of the classes have a unique field.
, but when dealing with unions of subclasses it doesn't work because a baseclass and subclass can't be in the same typing.Union
.
Do you have any suggestions on how to get the automatic disambiguator to work with a union of a subclass and baseclass? One idea is for structure()
to accept a frozenset
of types as an alternative to a typing.Union
.
What I Did
Here's the minimum code necessary to reproduce the problem:
from typing import Union
import attr
from cattr import structure, unstructure
@attr.s()
class BaseClass:
base_field: int = attr.ib()
@attr.s()
class SubClass(BaseClass):
sub_field: str = attr.ib()
BaseOrSub = Union[BaseClass, SubClass]
print(BaseOrSub) # <class '__main__.BaseClass'>
my_base = BaseClass(base_field=7)
print(structure(unstructure(my_base), BaseOrSub)) # BaseClass(foo=7)
my_sub = SubClass(base_field=8, sub_field="bar")
print(structure(unstructure(my_sub), BaseOrSub)) # TypeError: __init__() got an unexpected keyword argument 'bar'
Which gives this stack trace:
Traceback (most recent call last):
BaseClass(base_field=7)
File "/workplace/kevomara/Blackhawk/src/BlackhawkLambda/src/common/dict_mapping/cattr_union_issue.py", line 25, in <module>
print(structure(unstructure(my_sub), BaseOrSub)) # TypeError: __init__() got an unexpected keyword argument 'bar'
File "/workplace/kevomara/Blackhawk/env/BlackhawkLambda-1.0/test-runtime/lib/python3.6/site-packages/cattr/converters.py", line 178, in structure
return self._structure_func.dispatch(cl)(obj, cl)
File "/workplace/kevomara/Blackhawk/env/BlackhawkLambda-1.0/test-runtime/lib/python3.6/site-packages/cattr/converters.py", line 298, in structure_attrs_fromdict
return cl(**conv_obj)
TypeError: __init__() got an unexpected keyword argument 'sub_field'
I also tried both of these:
print(structure(unstructure(my_sub), [BaseClass, SubClass])) # TypeError: unhashable type: 'list'
print(structure(unstructure(my_sub), frozenset([BaseClass, SubClass]))) # ValueError: Unsupported type: frozenset({<class '__main__.SubClass'>, <class '__main__.BaseClass'>}). Register a structure hook for it.
Same issue. Can we please get help? Thanks!
Hello, I am facing the same issue, is there a possible workaround?
Since Python 3.7, explicit subclasses aren't removed from unions any longer. The code snippet from the OP works now:
❯ poetry run python a10.py
typing.Union[__main__.BaseClass, __main__.SubClass]
BaseClass(base_field=7)
SubClass(base_field=8, sub_field='bar')
So I'm closing this particular issue.