admin管理员组

文章数量:1404924

I'd like to serialize and deserialize an object holding a polymorphic member.

If the class was polymorphic, I would use a correct concrete type with TypeAdapter. The question is, how to do the same when a member is polymorphic. Here's a minimal example of what I mean:

from abc import ABC, abstractmethod
from pydantic import BaseModel, TypeAdapter


class I_Widget(ABC, BaseModel):
    @abstractmethod
    def do_stuff(self): ...


class WidgetHolder(BaseModel):
    widget: I_Widget
    

class ActualWidget(I_Widget):
    value: int

    def do_stuff(self):
        print('Doing stuff')


if __name__ == '__main__':
    w = ActualWidget(value=42)
    wh = WidgetHolder(widget=w)

    # Serializes:
    json = wh.json()

    # Deserializes:
    wh2 = TypeAdapter(WidgetHolder).validate_json(json) # Throws an error

When I run it, I get:

  File "/home/adam/.cache/pypoetry/virtualenvs/poc-py3.12/lib/python3.12/site-packages/pydantic/type_adapter.py", line 446, in validate_json
    return self.validator.validate_json(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Can't instantiate abstract class I_Widget without an implementation for abstract method 'do_stuff'

I'd like to serialize and deserialize an object holding a polymorphic member.

If the class was polymorphic, I would use a correct concrete type with TypeAdapter. The question is, how to do the same when a member is polymorphic. Here's a minimal example of what I mean:

from abc import ABC, abstractmethod
from pydantic import BaseModel, TypeAdapter


class I_Widget(ABC, BaseModel):
    @abstractmethod
    def do_stuff(self): ...


class WidgetHolder(BaseModel):
    widget: I_Widget
    

class ActualWidget(I_Widget):
    value: int

    def do_stuff(self):
        print('Doing stuff')


if __name__ == '__main__':
    w = ActualWidget(value=42)
    wh = WidgetHolder(widget=w)

    # Serializes:
    json = wh.json()

    # Deserializes:
    wh2 = TypeAdapter(WidgetHolder).validate_json(json) # Throws an error

When I run it, I get:

  File "/home/adam/.cache/pypoetry/virtualenvs/poc-py3.12/lib/python3.12/site-packages/pydantic/type_adapter.py", line 446, in validate_json
    return self.validator.validate_json(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Can't instantiate abstract class I_Widget without an implementation for abstract method 'do_stuff'
Share Improve this question edited Mar 22 at 17:10 mkrieger1 23.5k7 gold badges64 silver badges82 bronze badges asked Mar 22 at 15:36 Adam RyczkowskiAdam Ryczkowski 8,08414 gold badges50 silver badges73 bronze badges 2
  • This doesn't seem to serialize wh correctly in the first place; the resulting JSON just has an empty dict for the widget key, not anything that could be deserialized as an ActualWidget. I think this matters because what kind of I_Widget is the JSON supposed to be validated against? – chepner Commented Mar 22 at 17:10
  • @chepner I agree. My walkarond does serialize correctly. – Adam Ryczkowski Commented Mar 22 at 22:35
Add a comment  | 

2 Answers 2

Reset to default 1

Another possibility would be using a discriminator:

from abc import ABC, abstractmethod
from pydantic import BaseModel, TypeAdapter, Field
from typing import Literal, Union


class I_Widget(ABC, BaseModel):
    type: str

    @abstractmethod
    def do_stuff(self): ...


class ActualWidget(I_Widget):
    type: Literal["actual"] = "actual"  # Discriminator field
    value: int

    def do_stuff(self):
        print("Doing stuff")


class WidgetHolder(BaseModel):
    widget: Union[ActualWidget]  # Use Union to list all possible widget types


if __name__ == "__main__":
    w = ActualWidget(value=42)
    wh = WidgetHolder(widget=w)

    json = wh.model_dump_json()
    print("Serialized JSON:", json)

    wh2 = TypeAdapter(WidgetHolder).validate_json(json)
    assert wh2.widget.value == 42

I found one way around using a widget factory method.

It is not as clear and readable as I would hope for, but good enough to be proposed here.

Please, bring in alternative solutions...

from abc import ABC, abstractmethod
from pydantic import BaseModel, TypeAdapter
from typing import Type


class I_Widget(ABC, BaseModel):
    @abstractmethod
    def do_stuff(self): ...

class WidgetHolder(BaseModel):
    widget: I_Widget



class ActualWidget(I_Widget):
    value: int

    def do_stuff(self):
        print('Doing stuff')

def WidgetHolder_factory(widget_type: Type[I_Widget]):
    class _WidgetHolder(WidgetHolder):
        widget: widget_type
    return _WidgetHolder


if __name__ == '__main__':
    w = ActualWidget(value=42)
    wh = WidgetHolder_factory(ActualWidget)(widget=w)

    json = wh.json()

    wh2 = TypeAdapter(WidgetHolder_factory(ActualWidget)).validate_json(json)
    assert wh2.widget.value == 42

本文标签: pythonHow to specify actual class in Pydantic memberStack Overflow