Add generic dictionary merge routine.

- Poached from YamlUtils.
- YamlUtils should be updated to use this as the base merge routine.
This commit is contained in:
Jeffrey Townsend
2016-06-02 19:33:12 +00:00
parent 08267c2cde
commit d2f0cf340d

View File

@@ -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