From d3319368124576a07ea20e354802a7471fb9dcd8 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 1 Nov 2018 11:34:16 +0100 Subject: [PATCH] consolidate yaml configuration workaround ruamel.yaml issue 255, where once an empty dict or list has been written, 'flow' style is used thereafter, using dense `{key: value}` form instead of traditional yaml block style. --- tests/test_yaml.py | 19 +++++++++++++++++++ tljh/config.py | 9 ++++----- tljh/installer.py | 14 ++++++-------- tljh/yaml.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 tests/test_yaml.py create mode 100644 tljh/yaml.py diff --git a/tests/test_yaml.py b/tests/test_yaml.py new file mode 100644 index 0000000..1bdae0b --- /dev/null +++ b/tests/test_yaml.py @@ -0,0 +1,19 @@ +from tljh.yaml import yaml + + +def test_no_empty_flow(tmpdir): + path = tmpdir.join("config.yaml") + with path.open("w") as f: + f.write("{}") + # load empty config file + with path.open("r") as f: + config = yaml.load(f) + # set a value + config["key"] = "value" + # write to a file + with path.open("w") as f: + yaml.dump(config, f) + # verify that it didn't use compact '{}' flow-style + with path.open("r") as f: + content = f.read() + assert content.strip() == "key: value" diff --git a/tljh/config.py b/tljh/config.py index 090b916..9c862d5 100644 --- a/tljh/config.py +++ b/tljh/config.py @@ -13,14 +13,13 @@ tljh-config show firstlevel.second_level """ import argparse +from collections import Sequence, Mapping from copy import deepcopy import os import re import sys -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap, CommentedSeq -yaml = YAML(typ='rt') +from .yaml import yaml INSTALL_PREFIX = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh') @@ -211,11 +210,11 @@ def parse_value(value_str): def _is_dict(item): - return isinstance(item, (dict, CommentedMap)) + return isinstance(item, Mapping) def _is_list(item): - return isinstance(item, (list, CommentedSeq)) + return isinstance(item, Sequence) def main(argv=None): diff --git a/tljh/installer.py b/tljh/installer.py index 6f6ce4b..f0b7558 100644 --- a/tljh/installer.py +++ b/tljh/installer.py @@ -12,7 +12,6 @@ from urllib.error import HTTPError from urllib.request import urlopen, URLError import pluggy -from ruamel.yaml import YAML from tljh import ( apt, @@ -23,8 +22,7 @@ from tljh import ( traefik, user, ) - -from tljh.config import ( +from .config import ( CONFIG_DIR, CONFIG_FILE, HUB_ENV_PREFIX, @@ -32,10 +30,10 @@ from tljh.config import ( STATE_DIR, USER_ENV_PREFIX, ) +from .yaml import yaml HERE = os.path.abspath(os.path.dirname(__file__)) -rt_yaml = YAML() logger = logging.getLogger(__name__) @@ -263,7 +261,7 @@ def ensure_admins(admins): config_path = CONFIG_FILE if os.path.exists(config_path): with open(config_path, 'r') as f: - config = rt_yaml.load(f) + config = yaml.load(f) else: config = {} @@ -271,7 +269,7 @@ def ensure_admins(admins): config['users']['admin'] = list(admins) with open(config_path, 'w+') as f: - rt_yaml.dump(config, f) + yaml.dump(config, f) def ensure_jupyterhub_running(times=4): @@ -388,7 +386,7 @@ def ensure_config_yaml(plugin_manager): if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as f: - config = rt_yaml.load(f) + config = yaml.load(f) else: config = {} @@ -396,7 +394,7 @@ def ensure_config_yaml(plugin_manager): hook.tljh_config_post_install(config=config) with open(CONFIG_FILE, 'w+') as f: - rt_yaml.dump(config, f) + yaml.dump(config, f) def main(): diff --git a/tljh/yaml.py b/tljh/yaml.py new file mode 100644 index 0000000..3ff8a8d --- /dev/null +++ b/tljh/yaml.py @@ -0,0 +1,32 @@ +"""consolidated yaml API + +ensures the same yaml settings for reading/writing +throughout tljh +""" +from ruamel.yaml.composer import Composer +from ruamel.yaml import YAML + + +class _NoEmptyFlowComposer(Composer): + """yaml composer that avoids setting flow_style on empty + containers. + + workaround ruamel.yaml issue #255 + """ + + def compose_mapping_node(self, anchor): + node = super().compose_mapping_node(anchor) + if not node.value: + node.flow_style = False + return node + + def compose_sequence_node(self, anchor): + node = super().compose_sequence_node(anchor) + if not node.value: + node.flow_style = False + return node + + +# create the global yaml object: +yaml = YAML(typ="rt") +yaml.Composer = _NoEmptyFlowComposer