mashumaro
mashumaro copied to clipboard
Allow propagation of class based discriminator settings to subclasses
- mashumaro version: 3.12
- Python version: 3.11.7
- Operating System: Debian 12 (Bookworm)
Description
Issue Summary:
When serializing and deserializing a hierarchy of dataclasses involving abstract classes using mashumaro's to_dict()
and from_dict()
methods, attempting to deserialize a dictionary back to an object through an abstract parent class results in a TypeError
due to the inability to instantiate the abstract class, even when the dictionary represents a concrete subclass.
Expected Behavior: Deserialization of a dictionary to an object should be successful through any class in the hierarchy (including abstract classes) as long as the dictionary represents a concrete subclass that implements all abstract methods. This is expected to work because of the specified discriminator field that indicates the concrete subclass type.
Actual Behavior:
Deserializing a dictionary to an object through an abstract class using the from_dict()
method throws a TypeError
, indicating an attempt to instantiate an abstract class with unimplemented abstract methods.
What I Did
- Define a base class
Superclass
that inherits fromDataClassDictMixin
. This class includes aConfig
subclass with aDiscriminator
configured on fieldtype
and includes all subtypes. - Define an abstract class
AbstractSubclass
inheriting fromSuperclass
with an abstract methodfoo()
. - Define a concrete subclass
ConcreteSubclass
inheriting fromAbstractSubclass
, implementing thefoo()
method and setting the discriminator fieldtype
to"concrete_subclass"
. - Serialize an instance of
ConcreteSubclass
to a dictionary usingto_dict()
. - Attempt to deserialize the dictionary back to an object using
from_dict()
on all classes in the hierarchy.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from mashumaro import DataClassDictMixin
from mashumaro.config import BaseConfig
from mashumaro.types import Discriminator
@dataclass
class Superclass(DataClassDictMixin):
def __post_serialize__(self, d: dict) -> dict:
# Embed the discriminator into the serialized dictionary
return {"type": self.type, **d}
class Config(BaseConfig):
discriminator = Discriminator(field="type", include_subtypes=True)
@dataclass
class AbstractSubclass(Superclass, ABC):
weight: float
@abstractmethod
def foo(self) -> None:
raise NotImplementedError
# NOTE: If this was uncommented, the error disappears!
# class Config(BaseConfig):
# discriminator = Discriminator(field="type", include_subtypes=True)
@dataclass
class ConcreteSubclass(AbstractSubclass):
type = "concrete_subclass"
height: float
def foo(self) -> None:
print("bar")
inst = ConcreteSubclass(weight=80.0, height=180.0)
inst_dict = inst.to_dict()
assert inst_dict == {"type": "concrete_subclass", "weight": 80.0, "height": 180.0} # OK
assert inst == ConcreteSubclass.from_dict(inst_dict) # OK
assert inst == Superclass.from_dict(inst_dict) # OK
assert inst == AbstractSubclass.from_dict(
inst_dict
) # TypeError: Can't instantiate abstract class AbstractSubclass with abstract method foo
Error Message:
TypeError: Can't instantiate abstract class AbstractSubclass with abstract methods foo
Temporary Workaround:
Adding the same Config
subclass with a Discriminator
configuration to the AbstractSubclass
resolves the issue. This suggests a potential inconsistency in how discriminator configurations are inherited or recognized in the class hierarchy.