pythonyamlruamel.yaml

Fields not initialized when __post_init__() called using ruamel.yaml


I have two dataclasses: Msg and Field. Msg has a field fields of type list[Field]. I want to assign something to a field of each Field after they have all been initialized which is more or less their relative index in the fields list.

However, when I add a __post_init__(self) method to the Msg dataclass, the fields list is empty, so I can't update the indices.

from dataclasses import dataclass
from ruamel.yaml import YAML

@dataclass
class Msg:

    id: int
    desc: str
    fields: list[Field]

    def __post_init__(self) -> None:
        idx: int = 0
        for field in self.fields: # why is this empty??
            field.index = idx
            idx += field.size

@dataclass
class Field:
    id: int
    name: str
    units: str
    size: int
    index: int = -1

y = YAML()
y.register_class(Msg)
y.register_class(Field)

msg: Msg = y.load("""\
!Msg
id: 1
desc: status
fields:
- !Field
    id: 1
    name: Temp
    units: degC
    size: 2
""")

assert(msg.fields[0].index != -1) # fails :(

Why is this? How is the Msg being initialized without fields being initialized? Is there any way to do what I am trying to do using the class system? I am using Python 3.11 with ruamel.yaml 0.18.5 on MacOS.


Solution

  • By default, object serializers such as YAML and pickle have no idea what to do with the attribute mapping for a user-defined object other than to assign the mapping directly to the object's attribute dictionary as-is.

    This is why you can define a __setstate__ method for your class, so that ruamel.yaml's object constructor knows in this case to call the __init__ method with the mapping unpacked as arguments, which in turn calls __post_init__ for post-initialization:

    @dataclass
    class Msg:
        id: int
        desc: str
        fields: list[Field]
    
        def __post_init__(self) -> None:
            idx: int = 0
            for field in self.fields:
                field.index = idx
                idx += field.size
    
        def __setstate__(self, state):
            self.__init__(**state)
    

    Demo: https://replit.com/@blhsing1/PowerfulThoseJavadocs