Skip to content

Commit

Permalink
Add functionality for overriding dictionary merges.
Browse files Browse the repository at this point in the history
Dictionary merges can now be overridden at any level by usage
of a special character. The tilde is chosen as base default.
  • Loading branch information
Rtzq0 committed Jan 2, 2017
1 parent 2ac6452 commit 99acc6e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 8 deletions.
73 changes: 65 additions & 8 deletions reclass/datatypes/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -37,6 +37,7 @@ class Parameters(object):
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:
Expand Down Expand Up @@ -119,7 +120,25 @@ def _extend_list(self, cur, new, path):
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:
Expand All @@ -134,19 +153,42 @@ def _merge_dict(self, cur, new, path):
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:
Expand All @@ -157,8 +199,23 @@ def _merge_recurse(self, cur, new, path=None):
return self._update_scalar(cur, new, path)

def merge(self, other):
"""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 we're merging in a dict this is an initialization merge, so set
# the parameter accordingly
if isinstance(other, dict):
self._base = self._merge_recurse(self._base, other, None)
self._base = self._merge_recurse(self._base, other, None, initmerge=True)

elif isinstance(other, self.__class__):
self._base = self._merge_recurse(self._base, other._base,
Expand Down
12 changes: 12 additions & 0 deletions reclass/datatypes/tests/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ def test_merge_dicts_overwrite(self):
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):
Expand Down
1 change: 1 addition & 0 deletions reclass/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@

PARAMETER_INTERPOLATION_SENTINELS = ('${', '}')
PARAMETER_INTERPOLATION_DELIMITER = ':'
PARAMETER_DICT_KEY_OVERRIDE_PREFIX = '~'

0 comments on commit 99acc6e

Please sign in to comment.