Merge pull request #61 from Rtzq0/issue_60
Add functionality for overriding dictionary merges.
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index 37419fc..a39324e 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -7,8 +7,8 @@
# Released under the terms of the Artistic Licence 2.0
#
import types
-
-from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER
+from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER,\
+ PARAMETER_DICT_KEY_OVERRIDE_PREFIX
from reclass.utils.dictpath import DictPath
from reclass.utils.refvalue import RefValue
from reclass.errors import InfiniteRecursionError, UndefinedVariableError
@@ -37,6 +37,7 @@
functionality and does not try to be a really mapping object.
'''
DEFAULT_PATH_DELIMITER = PARAMETER_INTERPOLATION_DELIMITER
+ DICT_KEY_OVERRIDE_PREFIX = PARAMETER_DICT_KEY_OVERRIDE_PREFIX
def __init__(self, mapping=None, delimiter=None):
if delimiter is None:
@@ -47,7 +48,7 @@
if mapping is not None:
# we initialise by merging, otherwise the list of references might
# not be updated
- self.merge(mapping)
+ self.merge(mapping, initmerge=True)
delimiter = property(lambda self: self._delimiter)
@@ -119,7 +120,25 @@
ret.append(self._merge_recurse(None, new[i], path.new_subpath(offset + i)))
return ret
- def _merge_dict(self, cur, new, path):
+ def _merge_dict(self, cur, new, path, initmerge):
+ """Merge a dictionary with another dictionary.
+
+ Iterate over keys in new. If this is not an initialization merge and
+ the key begins with PARAMETER_DICT_KEY_OVERRIDE_PREFIX, override the
+ value of the key in cur. Otherwise deeply merge the contents of the key
+ in cur with the contents of the key in _merge_recurse over the item.
+
+ Args:
+ cur (dict): Current dictionary
+ new (dict): Dictionary to be merged
+ path (string): Merging path from recursion
+ initmerge (bool): True if called as part of entity init
+
+ Returns:
+ dict: a merged dictionary
+
+ """
+
if isinstance(cur, dict):
ret = cur
else:
@@ -134,19 +153,42 @@
ret.update(new)
return ret
+ ovrprfx = Parameters.DICT_KEY_OVERRIDE_PREFIX
+
for key, newvalue in new.iteritems():
- ret[key] = self._merge_recurse(ret.get(key), newvalue,
- path.new_subpath(key))
+ if key.startswith(ovrprfx) and not initmerge:
+ ret[key.lstrip(ovrprfx)] = newvalue
+ else:
+ ret[key] = self._merge_recurse(ret.get(key), newvalue,
+ path.new_subpath(key), initmerge)
return ret
- def _merge_recurse(self, cur, new, path=None):
+ def _merge_recurse(self, cur, new, path=None, initmerge=False):
+ """Merge a parameter with another parameter.
+
+ Iterate over keys in new. Call _merge_dict, _extend_list, or
+ _update_scalar depending on type. Pass along whether this is an
+ initialization merge.
+
+ Args:
+ cur (dict): Current dictionary
+ new (dict): Dictionary to be merged
+ path (string): Merging path from recursion
+ initmerge (bool): True if called as part of entity init, defaults
+ to False
+
+ Returns:
+ dict: a merged dictionary
+
+ """
+
if path is None:
path = DictPath(self.delimiter)
if isinstance(new, dict):
if cur is None:
cur = {}
- return self._merge_dict(cur, new, path)
+ return self._merge_dict(cur, new, path, initmerge)
elif isinstance(new, list):
if cur is None:
@@ -156,13 +198,27 @@
else:
return self._update_scalar(cur, new, path)
- def merge(self, other):
+ def merge(self, other, initmerge=False):
+ """Merge function (public edition).
+
+ Call _merge_recurse on self with either another Parameter object or a
+ dict (for initialization). Set initmerge if it's a dict.
+
+ Args:
+ other (dict or Parameter): Thing to merge with self._base
+
+ Returns:
+ None: Nothing
+
+ """
+
if isinstance(other, dict):
- self._base = self._merge_recurse(self._base, other, None)
+ self._base = self._merge_recurse(self._base, other,
+ None, initmerge)
elif isinstance(other, self.__class__):
self._base = self._merge_recurse(self._base, other._base,
- None)
+ None, initmerge)
else:
raise TypeError('Cannot merge %s objects into %s' % (type(other),
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index f58056d..5100639 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -188,6 +188,18 @@
goal.update(mergee)
self.assertDictEqual(p.as_dict(), dict(dict=goal))
+ def test_merge_dicts_override(self):
+ """Validate that tilde merge overrides function properly."""
+ mergee = {'~one': {'a': 'alpha'},
+ '~two': ['gamma']}
+ base = {'one': {'b': 'beta'},
+ 'two': ['delta']}
+ goal = {'one': {'a': 'alpha'},
+ 'two': ['gamma']}
+ p = Parameters(dict(dict=base))
+ p.merge(Parameters(dict(dict=mergee)))
+ self.assertDictEqual(p.as_dict(), dict(dict=goal))
+
def test_merge_dict_into_scalar(self):
p = Parameters(dict(base='foo'))
with self.assertRaises(TypeError):
diff --git a/reclass/defaults.py b/reclass/defaults.py
index d066290..fb04c83 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -26,3 +26,4 @@
PARAMETER_INTERPOLATION_SENTINELS = ('${', '}')
PARAMETER_INTERPOLATION_DELIMITER = ':'
+PARAMETER_DICT_KEY_OVERRIDE_PREFIX = '~'