pyyaml: ConstructorError - could not determine a constructor for custom tag (5.1)

The following code was working on 3.13 but no longer works in 5.1:

import yaml

class Ref(yaml.YAMLObject):
    yaml_tag = '!Ref'
    def __init__(self, val):
        self.val = val

    @classmethod
    def from_yaml(cls, loader, node):
        return cls(node.value)

yaml.load('Foo: !Ref bar')

I get the following exception on 5.1:

yaml.constructor.ConstructorError: could not determine a constructor for the tag '!Ref'
  in "<unicode string>", line 1, column 6:
    Foo: !Ref bar

This is true whether I use yaml.load, yaml.full_load or yaml.unsafe_load.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 6
  • Comments: 29 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Try

yaml.load('Foo: !Ref bar', Loader=yaml.Loader)

I’m not sure though if this is intentional. Will have a closer look later.

None of the workarounds above worked for me, I had to use this loader: Loader=yaml.BaseLoader on Python 3.7.0 PyYaml==5.1.2 or == 5.2b1

(edited) I reproduced using the same code: yaml.load('Foo: !Ref bar') This worked for me :yaml.load('Foo: !Ref bar', Loader=yaml.BaseLoader)

This is a breaking change, and is likely to break a ton of code out there that worked just fine: I spent myself a whole afternoon trying to figure out what I’d changed that broke all my tests.

Are you guys going to revert changes, or provide a better error message (at the very least)?

Today I encountered the same problem. Even the example straight from the documentation on the homepage does not work anymore

#!/usr/bin/env python
import yaml

class Monster(yaml.YAMLObject):
    yaml_tag = u'!Monster'
    def __init__(self, name, hp, ac, attacks):
        self.name = name
        self.hp = hp
        self.ac = ac
        self.attacks = attacks
    def __repr__(self):
        return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
            self.__class__.__name__, self.name, self.hp, self.ac, self.attacks)

yaml.unsafe_load("""
--- !Monster
name: Cave spider
hp: [2,6]    # 2d6
ac: 16
attacks: [BITE, HURT]
""")

Backtrace:

Traceback (most recent call last):
  File "./testfile", line 21, in <module>
    """)
  File "/home/user/.local/lib/python2.7/site-packages/yaml/__init__.py", line 182, in unsafe_load
    return load(stream, UnsafeLoader)
  File "/home/user/.local/lib/python2.7/site-packages/yaml/__init__.py", line 114, in load
    return loader.get_single_data()
  File "/home/user/.local/lib/python2.7/site-packages/yaml/constructor.py", line 45, in get_single_data
    return self.construct_document(node)
  File "/home/user/.local/lib/python2.7/site-packages/yaml/constructor.py", line 49, in construct_document
    data = self.construct_object(node)
  File "/home/user/.local/lib/python2.7/site-packages/yaml/constructor.py", line 94, in construct_object
    data = constructor(self, node)
  File "/home/user/.local/lib/python2.7/site-packages/yaml/constructor.py", line 420, in construct_undefined
    node.start_mark)
yaml.constructor.ConstructorError: could not determine a constructor for the tag '!Monster'
  in "<string>", line 2, column 5:
    --- !Monster
        ^

Edit: This seems to work (why?)

yaml.load(..., Loader=yaml.Loader)

I’m using version 5.3 like so:

import yaml

yaml.load("mgnl:variationOf: !weakreference 721ec351-e73a-489e-b467-e9d608468ebd", Loader=yaml.Loader)

But I still got the same error.

yaml.constructor.ConstructorError: could not determine a constructor for the tag '!weakreference'
  in "<unicode string>", line 1, column 19:
    mgnl:variationOf: !weakreference 721ec351-e73a-489 ...

I resolved the issue by following https://github.com/yaml/pyyaml/issues/266#issuecomment-559116876.

yaml.load("mgnl:variationOf: !weakreference 721ec351-e73a-489e-b467-e9d608468ebd", Loader=yaml.BaseLoader)

ok, I think I see it now. From https://github.com/yaml/pyyaml/blob/e471e86bf6dabdad45a1438c20a4a5c033eb9034/lib/yaml/__init__.py#L386, the loader for metaclass-driven custom tags defaults to yaml.Loader. The following also works for me:

import yaml

class Ref(yaml.YAMLObject):
    yaml_loader = yaml.SafeLoader
    yaml_tag = '!Ref'
    def __init__(self, val):
        self.val = val

    @classmethod
    def from_yaml(cls, loader, node):
        return cls(node.value)

yaml.safe_load('Foo: !Ref bar')

Same error for me. Worked using:

yaml.load('Foo: !Ref bar', Loader=yaml.BaseLoader)

Python 3.8.2 PyYAML 5.3.1

Python 3.6.10 PyYAML 5.3.1

The error is resolved when I use yaml.BaseLoader. However, !Ref is ignored and missing in the loaded content. It won’t work without class Ref, like @perlpunk mentioned.

@ryanbrookepayne BaseLoader only works because it is ignoring the weakreference tag. To do it correctly, you would have to use the loader for which you added the constructor.

safe_load doesn’t work for me but Loader=yaml.Loader does.

To ignore all tags (not yet defined) in SafeLoader:

def ignore(loader, tag, node):
    classname = node.__class__.__name__
    if (classname == 'SequenceNode'):
        resolved = loader.construct_sequence(node)
    elif (classname == 'MappingNode'):
        resolved = loader.construct_mapping(node)
    else:
        resolved = loader.construct_scalar(node)
    return resolved

yaml.add_multi_constructor('!', ignore, Loader=yaml.SafeLoader)
yaml.add_multi_constructor('', ignore, Loader=yaml.SafeLoader)

@vallamost I think the yaml.BaseLoader ignores 'em

@perlpunk This was a misunderstanding on my part, I hadn’t defined the Ref class in my example. So it works now! And my real code is working now too, thanks for your help.

@BonnieH @maru-podmast I can only reproduce this without using the original code at the top of the issue. Can you execute this code and show the output please?

import yaml

class Ref(yaml.YAMLObject):
    yaml_tag = '!Ref'
    def __init__(self, val):
        self.val = val

    @classmethod
    def from_yaml(cls, loader, node):
        return cls(node.value)

print(yaml.__version__)
data = yaml.load('Foo: !Ref bar', yaml.Loader)
print(data)
print(data['Foo'].val)

Without that class Ref of course it can’t work. BaseLoader simply ignores the unknown tags.

@maru-podmast sorry, I can’t reproduce. the original code works for me with 5.2b1 and 5.2:

yaml.load('Foo: !Ref bar', Loader=yaml.Loader)
yaml.load('Foo: !Ref bar', Loader=yaml.FullLoader)
yaml.load('Foo: !Ref bar') # emits warning

@maru-podmast do you have some code to reproduce?

I created #273