import logging import os import ruamel.yaml logger = logging.getLogger(__name__) def load_configuration(filename): ''' Load the given configuration file and return its contents as a data structure of nested dicts and lists. Raise ruamel.yaml.error.YAMLError if something goes wrong parsing the YAML, or RecursionError if there are too many recursive includes. ''' yaml = ruamel.yaml.YAML(typ='safe') yaml.Constructor = Include_constructor return yaml.load(open(filename)) def include_configuration(loader, filename_node): ''' Load the given YAML filename (ignoring the given loader so we can use our own), and return its contents as a data structure of nested dicts and lists. ''' return load_configuration(os.path.expanduser(filename_node.value)) class Include_constructor(ruamel.yaml.SafeConstructor): ''' A YAML "constructor" (a ruamel.yaml concept) that supports a custom "!include" tag for including separate YAML configuration files. Example syntax: `retention: !include common.yaml` ''' def __init__(self, preserve_quotes=None, loader=None): super(Include_constructor, self).__init__(preserve_quotes, loader) self.add_constructor('!include', include_configuration) def flatten_mapping(self, node): ''' Support the special case of shallow merging included configuration into an existing mapping using the YAML '<<' merge key. Example syntax: ``` retention: keep_daily: 1 <<: !include common.yaml ``` ''' representer = ruamel.yaml.representer.SafeRepresenter() for index, (key_node, value_node) in enumerate(node.value): if key_node.tag == u'tag:yaml.org,2002:merge' and value_node.tag == '!include': included_value = representer.represent_data(self.construct_object(value_node)) node.value[index] = (key_node, included_value) super(Include_constructor, self).flatten_mapping(node)