-
-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Converter ignores fields from child class when unstructuring (inheritance) #272
Comments
Interesting, what happens if you just unstructure an instance of |
That works fine:
|
Something's definitely fishy here. Will look into it. |
I am seeing the same behavior with my subclass that has extra fields. They are lost in the unstructuring. |
Actually there is a workaround. Using a similar example as the original one with The subclass problem with structuring and unstructuring
from typing import Union
from attrs import define
from cattrs import Converter, BaseConverter
@define
class Parent:
i: int
@define
class Child(Parent):
j: int
@define
class A:
a: Parent
c = Converter()
bc = BaseConverter()
# Trying unstructuring:
# ---------------------
print(c.unstructure(A(Child(1, 2)))) # {"a": {"i": 1}}, here Converter is wrong, {"a": {"i": 1, "j": 2}} is expected.
print(bc.unstructure(A(Child(1, 2)))) # {"a": {"i": 1, "j": 2}}, here BaseConverter is right!
# Trying structuring:
# -------------------
print(c.structure({"a": {"i": 1, "j": 2}}, C)) # A(a=Parent(i=1)), here Converter is wrong, A(a=Child(i=1, j=2)) is expected.
print(bc.unstructure({"a": {"i": 1, "j": 2}}, C)) # A(a=Parent(i=1)), here BaseConverter is wrong too! WorkaroundIn the above example, if you replace the type definition of attribute @define
class A:
a: Union[Parent, Child] Then all structuring and unstructuring tests with both converters above produce the expected results. I guess this workaround is only valid for Path forwardI tried to change the |
@Tinche I am seeing that you plan to cut a new release soon. I would like to have this issue fixed in this new release. Could you provide some guidance on how to tackle the problem. Is my idea of an automatic disambiguation of a Union type containing all subclass types is valid? I may be able to create a PR. |
@matmel I can take a look at this before the next release. But in any case, cattrs is designed so you can come up with a strategy without me having to include it in a release ;) |
Alright, I've reviewed the issue in depth. It doesn't matter whether attrs classes or dataclasses are used, or whether the classes are kwonly or frozen. Referencing the issue in the OP: on the surface, the issue is when cattrs encounters the On the face of it, you can solve the issue (with a small efficiency loss) by inserting the line converter.register_unstructure_hook(Bar, converter.unstructure_attrs_asdict) This will, in effect, use the runtime value of fields typed as I would advise against this, however. From a data modelling perspective this approach is not sound. If you have an instance of Another way of looking at it: if you continued with this approach, a subclass in the future can add any field what-so-ever, thus defeating the purpose of structured data. At that point you might as well go with a dictionary. The point of structured data is to know in advance the shape of your data. @matmel 's solution with a union is much better. (We haven't supported 3.6 for ~2 years now, so that's ok.) Using a union makes it very explicit about what the possible shapes of the data are, and in these cases it works out of the box. On a theoretical level it's a little funny to me to see a As for @matmel 's proposal for detecting subclasses, it's not really workable. Any class can gain subclasses after the unstructuring function is generated, TLDR; use a union for data-bearing classes. |
I've created PR #312 At my $job, I really need the ability to allow subclasses to be structured and unstructured in places where only the parent class is declared. After some time I realized that the manual union approach is not very maintanable in the long run. Subclassing and inheritance is a supported feature of python and as such I think it also deserves to be well supported in cattrs too. I find that accepting a subclass when the parent class is asked is well in the spirit of inheritance and Liskov substitution principle. In my case I know all the subclasses when I start to use the converter. I understand that using that idea with a plugin system based on inheritance is dangerous, but when you control all the class tree, I really think this is not a misfeature even in the face of data modeling perspective. |
Ok, I'm fine with adding this as long as it's opt-in (I think it'll be a speed loss in the general case). I'll look over your PR and let's see how to get this in. |
Thanks, convincing you was not too hard 😄 I think there is indeed a speed loss, mainly due to an additional test and optional function call in the case of unstructuring (but you would go through the union disambiguation in case of the "manual" subclass support), and for the structuring there is probably indeed a speed loss as you always go through the union disambiguation mechanism even if you are trying to structure the parent class and you were already in the parent class structuring function. |
Description
I'm serializing a dataclass with a field. If the field has a more generic type than the actual instance, all the information from the child class is lost when unstructuring.
What I Did
Expectation vs Reality
I'd expect it to output
unstructured {'foo': {'x': 1, 'y': 2}}
but it outputsunstructured {'foo': {'x': 1}}
Is this expected behaviour due to the way Converter generates unstructuring functions? If so, how can you change that behaviour?
The text was updated successfully, but these errors were encountered: