From d2f0cf340d89a7fd63832715558c65e856ab0962 Mon Sep 17 00:00:00 2001 From: Jeffrey Townsend Date: Thu, 2 Jun 2016 19:33:12 +0000 Subject: [PATCH] Add generic dictionary merge routine. - Poached from YamlUtils. - YamlUtils should be updated to use this as the base merge routine. --- .../src/python/onl/util/__init__.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/base/all/vendor-config-onl/src/python/onl/util/__init__.py b/packages/base/all/vendor-config-onl/src/python/onl/util/__init__.py index 0a96d689..c085b3f2 100644 --- a/packages/base/all/vendor-config-onl/src/python/onl/util/__init__.py +++ b/packages/base/all/vendor-config-onl/src/python/onl/util/__init__.py @@ -18,3 +18,74 @@ class OnlServiceMixin(object): self.logger.critical(msg) raise klass(msg) + +def dmerge(d1, d2): + """ + dictionary merge. + + d1 is the default source. Leaf values from d2 will override. + + d1 is the 'default' source; leaf values from d2 will override. + Returns the merged tree. + + Set a leaf in d2 to None to create a tombstone (discard any key + from d1). + + if a (sub) key in d1, d2 differ in type (dict vs. non-dict) then + the merge will proceed with the non-dict promoted to a dict using + the default-key schema ('='). Consumers of this function should be + prepared to handle such keys. + """ + merged = {} + q = [(d1, d2, merged)] + while True: + if not q: break + c1, c2, c3 = q.pop(0) + # add in non-overlapping keys + # 'None' keys from p2 are tombstones + s1 = set(c1.keys()) + s2 = set(c2.keys()) + + for k in s1.difference(s2): + v = c1[k] + if type(v) == dict: + c3.setdefault(k, {}) + q.append((v, {}, c3[k],)) + else: + c3.setdefault(k, v) + + for k in s2.difference(s1): + v = c2[k] + if v is None: continue + if type(v) == dict: + c3.setdefault(k, {}) + q.append(({}, v, c3[k],)) + else: + c3.setdefault(k, v) + + # handle overlapping keys + for k in s1.intersection(s2): + v1 = c1[k] + v2 = c2[k] + + if v2 is None: continue + + # two dicts, key-by-key reconciliation required + if type(v1) == dict and type(v2) == dict: + c3.setdefault(k, {}) + q.append((v1, v2, c3[k],)) + continue + + # two non-dicts, p2 wins + if type(v1) != dict and type(v2) != dict: + c3[k] = v2 + continue + + if type(v1) != dict: + v1 = { '=' : v1, } + if type(v2) != dict: + v2 = { '=' : v2, } + c3.setdefault(k, {}) + q.append((v1, v2, c3[k],)) + + return merged