title: Storing a tree of arbitrary objects in yaml file
body: <p>I simply want to use YAML to store my dedicated Python objects representing a configuration and load it back when needed.<br />
  My application allows to define a scenario that is made of nested Python objects.<br />
  The root object (name it Scenario) contains some attributes and a list of objects (name them Level1Type1, Level1Type2,...),
  and each of these Level1 objects is made also of some attributes and a list of objects (name them Level2Type1, Level2Type2,...).<br />
  To summarize, it is a tree. Each leaf has attributes and a list of other objects that are themselves leaves.<br />
  Moreover, only a part of the object's attributes, has to be saved in the file (dynamic attributes have no interest in a configuration file).<br />
  I decided to explicitly define which attributes are saved.</p>
  <p>Reading the documents, retrieved by google on the subject &quot;Python serialize objects with yaml&quot;, gave me some hints but let me confused
  on what is really needed.<br />
  Most of them are provided by <strong>Anthon</strong> (many thanks to him). It mainly explains why I used ruamel.yaml.<br />
  One regret: I spent too much time on that subject due to the lack of explanations and documents about Python arbitrary objects serialization.</p>
  <p>I noticed that the List objects in the scenario created with the object constructor becomes <code>CommentedSeq</code> objects when the scenario is reloaded using the YAML file.<br />
  I'm also wondering about the goal of the definition of <code>__repr__</code> that I saw in many examples. Is it used by serialization mechanism?</p>
  <p>Hereafter is the code validating my needs for my application.</p>
  <pre><code>import ruamel.yaml
  from io import StringIO
  
  yaml = ruamel.yaml.YAML()
  
  class Base:
      &quot;&quot;&quot;
      Base class for every class in the tree.
      &quot;&quot;&quot;
      # class variables can be necessary
      cst_value = &quot;common&quot;
  
      def __init__(self, elt_name=None, comment=None):
          self.elt_name = elt_name
          self.comment = comment
  
      def treatment(self):
          raise NotImplementedError('Base class should not be implemented')
  
  @yaml.register_class
  class Scenario(Base):
  
      yaml_tag = u'!Scenario'
  
      def __init__(self, elt_name=None, comment=None, level1_objs=None):
          super().__init__(elt_name, comment)
          # List of level1 objects: to be saved in yaml file.
          self.level1_objs = [] if level1_objs is None else level1_objs
  
      @classmethod
      def to_yaml(cls, representer, node):
          dict_representation = {
              'elt_name': node.elt_name,
              'comment': node.comment,
              'level1_objs': node.level1_objs
          }
          return representer.represent_mapping(cls.yaml_tag, dict_representation)
  
      @classmethod
      def from_yaml(cls, constructor, node):
          m = {}
          for m in constructor.construct_yaml_map(node):
              pass
          elt_name = m['elt_name'] if 'elt_name' in m else None
          comment = m['comment'] if 'comment' in m else None
          level1_objs = m['level1_objs'] if 'level1_objs' in m else None
          return cls(elt_name, comment, level1_objs)
  
      def treatment(self):
          pass
  
  
  class BunchOfData:
  
      def __init__(self):
          self.data_frame = None
          self.data1 = None
          self.data2 = None
          self.data3 = None
  
  
  @yaml.register_class
  class Level1Type1(Base):
  
      yaml_tag = u'!Level1Type1'
  
      def __init__(self, elt_name=None, comment=None, level2_objs=None, l1_t1_attr1=None):
          super().__init__(elt_name, comment)
          # List of level2 objects: to be saved in yaml file.
          self.level2_objs = [] if level2_objs is None else level2_objs
          # Attribute: to be saved in yaml file.
          self.l1_t1_attr1 = l1_t1_attr1
          # Dynamic attribute: Not to be saved in yaml file
          self.dyn_data = BunchOfData()
  
      @classmethod
      def to_yaml(cls, representer, node):
          dict_representation = {
              'elt_name': node.elt_name,
              'comment': node.comment,
              'l1_t1_attr1': node.l1_t1_attr1,
              'level2_objs': node.level2_objs
          }
          return representer.represent_mapping(cls.yaml_tag, dict_representation)
  
      @classmethod
      def from_yaml(cls, constructor, node):
          m = {}
          for m in constructor.construct_yaml_map(node):
              pass
          elt_name = m['elt_name'] if 'elt_name' in m else None
          comment = m['comment'] if 'comment' in m else None
          level2_objs = m['level2_objs'] if 'level2_objs' in m else None
          l1_t1_attr1 = m['l1_t1_attr1'] if 'l1_t1_attr1' in m else None
          return cls(elt_name, comment, level2_objs, l1_t1_attr1)
  
      def treatment(self):
          pass
  
  
  @yaml.register_class
  class Level1Type2(Base):
  
      yaml_tag = u'!Level1Type2'
  
      def __init__(self, elt_name=None, comment=None, level2_objs=None, l1_t2_attr1=None, l1_t2_attr2=None):
          super().__init__(elt_name, comment)
          # List of level2 objects: to be saved in yaml file.
          self.level2_objs = [] if level2_objs is None else level2_objs
          # Attribute: to be saved in yaml file.
          self.l1_t2_attr1 = l1_t2_attr1
          self.l1_t2_attr2 = l1_t2_attr2
          # Dynamic attribute: Not to be saved in yaml file
          self.dyn_data = BunchOfData()
  
      @classmethod
      def to_yaml(cls, representer, node):
          dict_representation = {
              'elt_name': node.elt_name,
              'comment': node.comment,
              'level2_objs': node.level2_objs,
              'l1_t2_attr1': node.l1_t2_attr1,
              'l1_t2_attr2': node.l1_t2_attr2
          }
          return representer.represent_mapping(cls.yaml_tag, dict_representation)
  
      @classmethod
      def from_yaml(cls, constructor, node):
          m = {}
          for m in constructor.construct_yaml_map(node):
              pass
          elt_name = m['elt_name'] if 'elt_name' in m else None
          comment = m['comment'] if 'comment' in m else None
          level2_objs = m['level2_objs'] if 'level2_objs' in m else None
          l1_t2_attr1 = m['l1_t2_attr1'] if 'l1_t2_attr1' in m else None
          l1_t2_attr2 = m['l1_t2_attr2'] if 'l1_t2_attr2' in m else None
          return cls(elt_name, comment, level2_objs, l1_t2_attr1, l1_t2_attr2)
  
      def treatment(self):
          pass
  
  
  @yaml.register_class
  class Level2Type1(Base):
  
      yaml_tag = u'!Level2Type1'
  
      def __init__(self, elt_name=None, comment=None, l2_t1_attr1=None):
          super().__init__(elt_name, comment)
          # Attribute: to be saved in yaml file.
          self.l2_t1_attr1 = l2_t1_attr1
  
      @classmethod
      def to_yaml(cls, representer, node):
          dict_representation = {
              'elt_name': node.elt_name,
              'comment': node.comment,
              'l2_t1_attr1': node.l2_t1_attr1
          }
          return representer.represent_mapping(cls.yaml_tag, dict_representation)
  
      @classmethod
      def from_yaml(cls, constructor, node):
          m = {}
          for m in constructor.construct_yaml_map(node):
              pass
          elt_name = m['elt_name'] if 'elt_name' in m else None
          comment = m['comment'] if 'comment' in m else None
          l2_t1_attr1 = m['l2_t1_attr1'] if 'l2_t1_attr1' in m else None
          return cls(elt_name, comment, l2_t1_attr1)
  
      def treatment(self):
          pass
  
  
  @yaml.register_class
  class Level2Type2(Base):
  
      yaml_tag = u'!Level2Type2'
  
      def __init__(self, elt_name=None, comment=None, l2_t2_attr1=None, l2_t2_attr2=None):
          super().__init__(elt_name, comment)
          # Attribute: to be saved in yaml file.
          self.l2_t2_attr1 = l2_t2_attr1
          self.l2_t2_attr2 = l2_t2_attr2
  
      @classmethod
      def to_yaml(cls, representer, node):
          dict_representation = {
              'elt_name': node.elt_name,
              'comment': node.comment,
              'l2_t2_attr1': node.l2_t2_attr1,
              'l2_t2_attr2': node.l2_t2_attr2
          }
          return representer.represent_mapping(cls.yaml_tag, dict_representation)
  
      @classmethod
      def from_yaml(cls, constructor, node):
          m = {}
          for m in constructor.construct_yaml_map(node):
              pass
          elt_name = m['elt_name'] if 'elt_name' in m else None
          comment = m['comment'] if 'comment' in m else None
          l2_t2_attr1 = m['l2_t2_attr1'] if 'l2_t2_attr1' in m else None
          l2_t2_attr2 = m['l2_t2_attr2'] if 'l2_t2_attr2' in m else None
          return cls(elt_name, comment, l2_t2_attr1, l2_t2_attr2)
  
      def treatment(self):
          pass
  
  
  @yaml.register_class
  class Level2Type3(Base):
  
      yaml_tag = u'!Level2Type3'
  
      def __init__(self, elt_name=None, comment=None, l2_t3_attr1=None, l2_t3_attr2=None, l2_t3_attr3=None):
          super().__init__(elt_name, comment)
          # Attribute: to be saved in yaml file.
          self.l2_t3_attr1 = l2_t3_attr1
          self.l2_t3_attr2 = l2_t3_attr2
          self.l2_t3_attr3 = l2_t3_attr3
  
      @classmethod
      def to_yaml(cls, representer, node):
          dict_representation = {
              'elt_name': node.elt_name,
              'comment': node.comment,
              'l2_t3_attr1': node.l2_t3_attr1,
              'l2_t3_attr2': node.l2_t3_attr2,
              'l2_t3_attr3': node.l2_t3_attr3
          }
          return representer.represent_mapping(cls.yaml_tag, dict_representation)
  
      @classmethod
      def from_yaml(cls, constructor, node):
          m = {}
          for m in constructor.construct_yaml_map(node):
              pass
          elt_name = m['elt_name'] if 'elt_name' in m else None
          comment = m['comment'] if 'comment' in m else None
          l2_t3_attr1 = m['l2_t3_attr1'] if 'l2_t3_attr1' in m else None
          l2_t3_attr2 = m['l2_t3_attr2'] if 'l2_t3_attr2' in m else None
          l2_t3_attr3 = m['l2_t3_attr3'] if 'l2_t3_attr3' in m else None
          return cls(elt_name, comment, l2_t3_attr1, l2_t3_attr2, l2_t3_attr3)
  
      def treatment(self):
          pass
  
  # Make this run.
  test = Scenario(&quot;my_scenario&quot;, &quot;what a scenario may look like after yaml dump&quot;,
                  [Level1Type1(&quot;l1_t1_object&quot;, &quot;I am a Level1 Type1 object&quot;, [
                      Level2Type1(&quot;l2_t1_object&quot;, &quot;I am a Level2 Type1 object&quot;, 11211),
                      Level2Type2(&quot;l2_t2_object&quot;, &quot;I am a Level2 Type2 object&quot;, 11221, 11222),
                      Level2Type3(&quot;l2_t3_object&quot;, &quot;I am a Level2 Type3 object&quot;, 11231, 11232, 11233),
                  ], 111),
                   Level1Type2(&quot;l1_t2_object&quot;, &quot;I am a Level1 Type2 object&quot;, [
                       Level2Type2(&quot;l2_t2_object&quot;, &quot;I am a Level2 Type2 object&quot;, 12221, 12222),
                       Level2Type1(&quot;l2_t1_object&quot;, &quot;I am a Level2 Type1 object&quot;, 12211),
                       Level2Type3(&quot;l2_t3_object&quot;, &quot;I am a Level2 Type3 object&quot;, 12231, 12232, 12233),
                   ], 121, 122)
                   ]
                  )
  # serialize
  dump_buf = StringIO()
  yaml.dump(test, dump_buf)
  test_serialized = dump_buf.getvalue()
  print(test_serialized)
  # deserialize
  test_is_back = yaml.load(test_serialized)
  print(test_is_back)
  </code></pre>
  <p>The produced yaml file looks like:</p>
  <pre><code>!Scenario
  elt_name: my_scenario
  comment: what a scenario may look like after yaml dump
  level1_objs:
  - !Level1Type1
    elt_name: l1_t1_object
    comment: I am a Level1 Type1 object
    l1_t1_attr1: 111
    level2_objs:
    - !Level2Type1
      elt_name: l2_t1_object
      comment: I am a Level2 Type1 object
      l2_t1_attr1: 11211
    - !Level2Type2
      elt_name: l2_t2_object
    ....
  
  </code></pre>