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?
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.
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'])