Loading custom objects with PyYAML
Question:
I have the following YAML document:
!!com.example.hero.YAMLAnimals
animals:
Land: [Cow, Lion]
Sea: [Salmon, Cod]
I expected to be able to load the animals
object by subclassing YAMLObject
.
class YAMLAnimals(yaml.YAMLObject):
yaml_tag = u'!com.example.hero.YAMLAnimals'
def __init__(self, animals):
self.animals = animals
However, when I load the document, I get a ConstructorError
.
ConstructorError: could not determine a constructor for the tag
'tag:yaml.org,2002:com.example.hero.YAMLAnimals'
Answers:
According to the YAML Reference Card, using the double exclamation point prefix will add tag:yaml.org,2002:
to the front of your tag. Instead, use a single exclamation point to properly define your tag.
Animals: !com.example.hero.YAMLAnimals
Land: [Cow, Lion]
Sea: [Salmon, Cod]
class YAMLAnimals(yaml.YAMLObject):
yaml_tag = '!com.example.hero.YAMLAnimals'
Also, as @mgrollins points out, you need to pass the Loader
argument to yaml.load
.
document = yaml.load(..., Loader = yaml.Loader)
>>> Animals = document['Animals']
>>> print(Animals)
<__main__.YAMLAnimals object at 0x1098c2dd0>
>>> print(vars(Animals))
{'Land': ['Cow', 'Lion'], 'Sea': ['Salmon', 'Cod']}
>>> print(Animals.Land)
['Cow', 'Lion']
>>> print(Animals.Sea)
['Salmon', 'Cod']
Consider using yaml.add_constructor
when defining your tag; along with a namedtuple
.
class YAMLAnimals(collections.namedtuple('Animals', 'Land, Sea')):
def __new__(cls, loader, node):
return super().__new__(cls, **loader.construct_mapping(node))
yaml.add_constructor('!com.example.hero.YAMLAnimals', YAMLAnimals)
>>> document = yaml.unsafe_load("""
... Animals: !com.example.hero.YAMLAnimals
... Land: [Cow, Lion]
... Sea: [Salmon, Cod]
... """)
>>> print(Animals := document['Animals'])
YAMLAnimals(Land=['Cow', 'Lion'], Sea=['Salmon', 'Cod'])
>>> print(Animals._asdict())
{'Land': ['Cow', 'Lion'], 'Sea': ['Salmon', 'Cod']}
I have the following YAML document:
!!com.example.hero.YAMLAnimals
animals:
Land: [Cow, Lion]
Sea: [Salmon, Cod]
I expected to be able to load the animals
object by subclassing YAMLObject
.
class YAMLAnimals(yaml.YAMLObject):
yaml_tag = u'!com.example.hero.YAMLAnimals'
def __init__(self, animals):
self.animals = animals
However, when I load the document, I get a ConstructorError
.
ConstructorError: could not determine a constructor for the tag
'tag:yaml.org,2002:com.example.hero.YAMLAnimals'
According to the YAML Reference Card, using the double exclamation point prefix will add tag:yaml.org,2002:
to the front of your tag. Instead, use a single exclamation point to properly define your tag.
Animals: !com.example.hero.YAMLAnimals
Land: [Cow, Lion]
Sea: [Salmon, Cod]
class YAMLAnimals(yaml.YAMLObject):
yaml_tag = '!com.example.hero.YAMLAnimals'
Also, as @mgrollins points out, you need to pass the Loader
argument to yaml.load
.
document = yaml.load(..., Loader = yaml.Loader)
>>> Animals = document['Animals']
>>> print(Animals)
<__main__.YAMLAnimals object at 0x1098c2dd0>
>>> print(vars(Animals))
{'Land': ['Cow', 'Lion'], 'Sea': ['Salmon', 'Cod']}
>>> print(Animals.Land)
['Cow', 'Lion']
>>> print(Animals.Sea)
['Salmon', 'Cod']
Consider using yaml.add_constructor
when defining your tag; along with a namedtuple
.
class YAMLAnimals(collections.namedtuple('Animals', 'Land, Sea')):
def __new__(cls, loader, node):
return super().__new__(cls, **loader.construct_mapping(node))
yaml.add_constructor('!com.example.hero.YAMLAnimals', YAMLAnimals)
>>> document = yaml.unsafe_load("""
... Animals: !com.example.hero.YAMLAnimals
... Land: [Cow, Lion]
... Sea: [Salmon, Cod]
... """)
>>> print(Animals := document['Animals'])
YAMLAnimals(Land=['Cow', 'Lion'], Sea=['Salmon', 'Cod'])
>>> print(Animals._asdict())
{'Land': ['Cow', 'Lion'], 'Sea': ['Salmon', 'Cod']}