How to make a custom YAML tag work with a sequence alias in pyyaml

拥有回忆 提交于 2019-12-11 07:53:30

问题


I’ve got a yaml file with aliases like this:

vars:
  users: &users ['user1', 'user2', 'user3', 'user4']

refs:
  users: *users
  user: !rand ['user1', 'user2', 'user3', 'user4']

!rand is a custom tag that calls python’s random.choice on a yaml sequence.

I’m using the following implementation of the !rand tag:

import random
import yaml

def load_yaml(file):
    def rand_constructor(loader, node):
        value = loader.construct_sequence(node)
        return random.choice(value)

    yaml.add_constructor('!rand', rand_constructor)

    with open(file) as f:
        return yaml.load(f)

It works as expected, and user gets a random value from users. Now, when I use !rand with the alias *users it stops working:

vars:
  users: &users ['user1', 'user2', 'user3', 'user4']

refs:
  users: *users
  user: !rand *users

The error is:

File ../python3.6/site-packages/yaml/parser.py", line 439, in parse_block_mapping_key
    "expected <block end>, but found %r" % token.id, token.start_mark)
yaml.parser.ParserError: while parsing a block mapping
  in "/temp/config.yml", line 5, column 3
expected <block end>, but found '<alias>'
  in "/temp/config.yml", line 6, column 18

How do I make it work with sequence aliases?


回答1:


In YAML, tags are designed to define content type. They are not designed to process content.

For this reason, you cannot tag an alias, since an alias is just a reference to content which has already been tagged at the location where it's defined. In your case, your sequence will get the !!seq tag according to the YAML core schema. YAML does not provide the ability to re-tag it.

Having said that, you can of course do a workaround: Wrap the !rand parameter in a sequence:

vars:
  users: &users ['user1', 'user2', 'user3', 'user4']

refs:
  users: *users
  user: !rand [*users]

You then just need to change your code to:

import random
import yaml

def load_yaml(file):
    def rand_constructor(loader, node):
        value = loader.construct_sequence(node)
        return random.choice(value[0])

    yaml.add_constructor('!rand', rand_constructor)

    with open(file) as f:
        return yaml.load(f)

But be aware that a direct call to !rand then looks like this:

vars:
  users: &users ['user1', 'user2', 'user3', 'user4']

refs:
  users: *users
  user: !rand [['user1', 'user2', 'user3', 'user4']]

See it like the outer sequence being the parameter list of the function !rand (which takes one parameter) and the inner sequence being that parameter.




回答2:


You can also make your list callable and call it each time you need random user:

YAML = """
user: &users !rand 
    - user1
    - user2
    - user3
    - user4
"""

import random
import yaml
from collections.abc import Sequence

class RandomizableList(Sequence):
    def __init__(self, items):
        self.items = items
    def __len__(self):
        return len(self.items)
    def __getitem__(self, value):
        return self.items[value]
    def __call__(self):
        return random.choice(self.items)
    def __repr__(self):
        return repr(self.items)

def rand_constructor(loader, node):
    return RandomizableList(loader.construct_sequence(node))

yaml.add_constructor('!rand', rand_constructor)
result = yaml.load(YAML)
for i in range(4):
    print(result['user']())
print(result['user'])


来源:https://stackoverflow.com/questions/53776602/how-to-make-a-custom-yaml-tag-work-with-a-sequence-alias-in-pyyaml

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!