| # Copyright 2013 - 2017 Mirantis, Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. You may obtain |
| # a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| |
| import json |
| import os |
| import yaml |
| |
| |
| def get_nested_key(data, path=None): |
| if type(path) is not list: |
| raise("Use 'list' object with key names for 'path'") |
| for key in path: |
| value = data.get(key, None) |
| if value is not None: |
| data = value |
| else: |
| return None |
| return data |
| |
| |
| def create_nested_key(data, path=None, value=None): |
| if type(data) is not dict: |
| raise("Use 'dict' object for 'data'") |
| if type(path) is not list: |
| raise("Use 'list' object with key names for 'path'") |
| for key in path[:-1]: |
| if key not in data: |
| data[key] = {} |
| data = data[key] |
| data[path[-1]] = value |
| |
| |
| def remove_nested_key(data, path=None): |
| if type(path) is not list: |
| raise("Use 'list' object with key names for 'path'") |
| |
| # Remove the value from the specified key |
| val = get_nested_key(data, path[:-1]) |
| val[path[-1]] = None |
| |
| # Clear parent keys if empty |
| while path: |
| val = get_nested_key(data, path) |
| if val is not None: |
| # Non-empty value, nothing to do |
| return |
| else: |
| get_nested_key(data, path[:-1]).pop(path[-1]) |
| path = path[:-1] |
| |
| |
| def yaml_read(yaml_file): |
| if os.path.isfile(yaml_file): |
| with open(yaml_file, 'r') as f: |
| return yaml.load(f) |
| else: |
| print("\'{}\' is not a file!".format(yaml_file)) |
| |
| |
| def json_read(yaml_file): |
| if os.path.isfile(yaml_file): |
| with open(yaml_file, 'r') as f: |
| return json.load(f) |
| else: |
| print("\'{}\' is not a file!".format(yaml_file)) |
| |
| |
| def merge_nested_objects(obj_1, obj_2): |
| """Merge two objects with optional key overwrites |
| |
| Original : https://stackoverflow.com/a/17860173 |
| - Merges dicts and lists |
| - If a dict key has the suffix '__overwrite__' and boolean value, |
| then the key is assumed as a special keyword for merging: |
| <key>__overwrite__: True # Overwrite the existing <key> content |
| # with <key> from obj_2 |
| <key>__overwrite__: False # Keep the existing <key> content from obj_1 |
| |
| |
| Case #1: Merge dicts and lists, overwrite other types with latest value |
| |
| dict_a = { |
| 'host': '1.1.1.1', |
| 'ssh': { |
| 'login': 'user' |
| } |
| } |
| |
| dict_b = { |
| 'host': '2.2.2.2', |
| 'ssh': { |
| 'password': 'pass' |
| } |
| } |
| |
| print(merge_nested_objects(dict_a, dict_b)) |
| { |
| 'host': '2.2.2.2', |
| 'ssh': { |
| 'login': 'user', |
| 'password': 'pass', |
| } |
| } |
| |
| Case #2: Use <key>__overwrite__: True to remove previous key content |
| |
| dict_a = { |
| 'host': '1.1.1.1' |
| 'ssh': { |
| 'login': 'user' |
| } |
| } |
| |
| dict_b = { |
| 'ssh__overwrite__': True |
| 'ssh': { |
| 'password': 'pass' |
| } |
| } |
| |
| print(merge_nested_objects(dict_a, dict_b)) |
| { |
| 'host': '1.1.1.1', |
| 'ssh': { |
| 'password': 'pass', |
| } |
| } |
| |
| Case #3: Use <key>__overwrite__: False to skip merging key |
| if already exists |
| |
| dict_a = { |
| 'host': '1.1.1.1' |
| 'ssh': { |
| 'login': 'user' |
| } |
| } |
| |
| dict_b = { |
| 'host__overwrite__': False |
| 'host': '2.2.2.2' |
| 'ssh': { |
| 'login__overwrite__': False |
| 'login': 'new_user' |
| 'password': 'pass' |
| } |
| } |
| |
| print(merge_nested_objects(dict_a, dict_b)) |
| { |
| 'host': '1.1.1.1', |
| 'ssh': { |
| 'login': 'user', |
| 'password': 'pass' |
| } |
| } |
| |
| |
| """ |
| # Merge two dicts |
| if isinstance(obj_1, dict) and isinstance(obj_2, dict): |
| result = {} |
| for key, value in obj_1.iteritems(): |
| if key not in obj_2: |
| result[key] = value |
| else: |
| overwrite_key = key + '__overwrite__' |
| if overwrite_key in obj_2 and obj_2[overwrite_key] is True: |
| result[key] = obj_2[key] |
| elif overwrite_key in obj_2 and obj_2[overwrite_key] is False: |
| result[key] = value |
| else: |
| result[key] = merge_nested_objects(value, obj_2[key]) |
| for key, value in obj_2.iteritems(): |
| if key not in obj_1: |
| result[key] = value |
| return result |
| |
| # Add two lists |
| if isinstance(obj_1, list) and isinstance(obj_2, list): |
| return obj_1 + list(set(obj_2) - set(obj_1)) |
| |
| # Overwrite a value with new one |
| return obj_2 |