I have a yaml file describing a series of nested objects that I am working on importing. Because I want to have __init__ on each get called, I am writing custom constructors
def volume_constructor(loader, node):
instance = Volume.__new__(Volume)
yield instance
state = loader.construct_mapping(node)
instance.__init__(**state)
Then when I am reading in the yaml file I am adding the constructors before doing the load:
yaml.add_constructor(volume_yaml_tag, volume_constructor, yaml.SafeLoader)
yaml.add_constructor(host_yaml_tag, volume_constructor, yaml.SafeLoader)
configcontent = configfile.read()
cfg = yaml.safe_load(configcontent)
In this case the volume is the parent object which contains several properties as normal strings etc along with a list of host objects. The excerpt below shows the general form with values omitted for brevity
- !!python/object:libraries.volume.Volume
_Volume__consuminghostlist:
- !!python/object:libraries.host.Host
_Host__property1: fc
_Host__property2: localhost
_Volume__property1: false
_Volume__property2: sometextvalue
_Volume__property3: somenumericvalue
I can define and dump the objects without issue however when I attempt the load with the custom constructors as defined above I get the error stack below. Note that when I change the order of the properties so that the first one processed isn't a nested object the error still flags the first thing processed.
cfg = yaml.safe_load(configcontent)
File "....yaml\__init__.py", line 162, in safe_load
return load(stream, SafeLoader)
File "...yaml\__init__.py", line 114, in load
return loader.get_single_data()
File "...yaml\constructor.py", line 43, in get_single_data
return self.construct_document(node)
File "...yaml\constructor.py", line 52, in construct_document
for dummy in generator:
File "...volume.py", line 15, in volume_constructor
instance.__init__(**state)
TypeError: __init__() got an unexpected keyword argument '_Volume__consuminghostlist'
If however I forgo adding the custom constructors and do a stock load, the load is successful though at that point __init__ for each is no longer called. Is there another way I should be going about having __init__ invoked or is there something wrong with the way the constructor is setup? I imagine there must be some way to avoid doing a manual parsing of each tag on the constructor and feeding them as individual parameters to init_. I toyed with setting the deepcopy property but it didn't make a difference. For reference the config file was created using:
yaml.dump(cfg, sys.stdout, Dumper=noalias_dumper, default_flow_style=False)
update: Several responses mentioned the invalid yaml formatting which I agree doesn't look right so I did some experimenting to see why that was happening. I created the yaml by defining the objects and then dumping them rather than writing it by hand so I would have expected the dump to use valid yaml formatting. I found as shown below that this gets mangled when I have @property decorators setup for a property
import yaml
import sys
class Volume(yaml.YAMLObject):
yaml_loader = yaml.SafeLoader
def __init__(self, input_property1, input_property2):
self.property1 = input_property1
self.property2 = input_property2
@property
def property1(self):
return self.__property1
@property1.setter
def property1(self, input_property1):
self.__property1 = input_property1
samplevolume = Volume("ABC", 1.0)
noalias_dumper = yaml.dumper.Dumper
noalias_dumper.ignore_aliases = lambda self, data: True
yaml.dump(samplevolume, sys.stdout, Dumper=noalias_dumper, default_flow_style=False)
when I run the sample code above I get the dump output below showing the expected treatment for the property that doesn't have @property definitions
!!python/object:__main__.Volume
_Volume__property1: ABC
property2: 1.0
updated 2 Based on the discussion with @flyx I tried aligning the names as shown below
import yaml
import sys
class Volume(yaml.YAMLObject):
yaml_loader = yaml.SafeLoader
def __init__(self, property1, property2):
self.property1 = property1
self.property2 = property2
@property
def property1(self):
return self.property1
@property1.setter
def property1(self, property1):
self.__property1 = property1
samplevolume = Volume("ABC", 1.0)
noalias_dumper = yaml.dumper.Dumper
noalias_dumper.ignore_aliases = lambda self, data: True
yaml.dump(samplevolume, sys.stdout, Dumper=noalias_dumper, default_flow_style=False, allow_unicode=True)
however it doesn't change the output formatting
!!python/object:__main__.Volume
_Volume__property1: ABC
property2: 1.0
given that using property decorators is a core feature of python I'm surprised that pyyaml requires custom representers and constructors to deal with it.
Volume.