cattrs
cattrs copied to clipboard
Unstructuring subclasses
- cattrs version: 22.2.0
- Python version: 3.8
- Operating System: macOS
Description
Hi @Tinche, I am currently working on several projects where I need to (un)structure objects of subclasses of a common base class. Digging through the documentation and also some issues here on Github, I eventually found your example how this can be done with the current version of cattrs. However, I think I have found two problems (bugs? unsupported features?) with the approach and would love to hear your opinion.
But before I go into the details, a quick question upfront: Is this going to remain the "recommended" approach to unstructure objects from a class hierarchy? I am asking because I also saw #312 which seems about to be merged but I am not entirely sure how/if it's going to replace your described method.
So here are now the two issues I noticed:
- With the version described above, your example no longer seems to work. In order to get it running, one now needs to explicitly set
slotted=False. I guess this is probably due to due the changes defaults (see also #267)? On the other hand, I wonder why your example then originally worked because it already usesdefineinstead ofattr.s? - Still, I struggled a lot to get it running in my own code until I finally found the problem: The approach crashes when using
from __future__ import annotationsand throws the following exception:
Traceback (most recent call last):
File "/usr/local/Caskroom/mambaforge/base/envs/baybe/lib/python3.8/site-packages/cattrs/converters.py", line 461, in _structure_attribute
return self._structure_func.dispatch(type_)(value, type_)
File "/usr/local/Caskroom/mambaforge/base/envs/baybe/lib/python3.8/site-packages/cattrs/converters.py", line 377, in _structure_error
raise StructureHandlerNotFoundError(msg, type_=cl)
cattrs.errors.StructureHandlerNotFoundError: Unsupported type: 'int'. Register a structure hook for it.
Is the second issue related to #215 or is this a bug?
A little difficult to answer these without concrete examples.
I think #312 is a better bet than the old snippets since it's a more sophisticated approach, and it's a better base for experimentation.
Thanks for your reply! The code example is really just your code + the future import:
from __future__ import annotations
from attr import define
from cattr import GenConverter
c = GenConverter()
@define
class Base:
a: int
@define
class SubOne(Base):
b: int
@define
class SubTwo(Base):
c: int
def unstructure_base(b):
return {"_type": b.__class__.__name__, **c.unstructure_attrs_asdict(b)}
c.register_unstructure_hook(Base, unstructure_base)
print(c.unstructure(SubOne(1, 2)))
def structure_base(val, _):
sub_name = val.pop("_type")
for klass in Base.__subclasses__():
if klass.__name__ == sub_name:
return c.structure_attrs_fromdict(val, klass)
return c.structure_attrs_fromdict(val, Base)
c.register_structure_hook(Base, structure_base)
print(c.structure(c.unstructure(SubOne(1, 2)), Base))
print(c.structure(c.unstructure(SubTwo(1, 2)), Base))
print(c.structure(c.unstructure(Base(1)), Base))
However, I've now also tested with python 3.11 and there everything works as expected! So the issue seems really related to how the future imports are handled in older python versions 🤔 Nonetheless, it might be worth mentioning this somewhere in the docs once one of us figures out the source of the problem...