deal with ancestor references before deeper references
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index ece5b1c..91016d2 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -260,9 +260,10 @@
# _interpolate_inner removes references from the refs hash after
# processing them, so we cannot just iterate the dict
path, value = self._unrendered.iteritems().next()
- self._interpolate_inner(path, path.get_value(self._base), options)
+ self._interpolate_inner(path, options)
- def _interpolate_inner(self, path, value, options):
+ def _interpolate_inner(self, path, options):
+ value = path.get_value(self._base)
if not isinstance(value, (Value, ValueList)):
# references to lists and dicts are only deepcopied when merged
# together so it's possible a value with references in a referenced
@@ -282,8 +283,14 @@
# faced with a cyclical reference.
raise InfiniteRecursionError(path, ref)
else:
- value_inner = path_from_ref.get_value(self._base)
- self._interpolate_inner(path_from_ref, value_inner, options)
+ self._interpolate_inner(path_from_ref, options)
+ else:
+ # ensure ancestor keys are already dereferenced
+ ancestor = DictPath(self.delimiter)
+ for k in path_from_ref.key_parts():
+ ancestor = ancestor.new_subpath(k)
+ if ancestor in self._unrendered:
+ self._interpolate_inner(ancestor, options)
if value.allRefs():
# all references have been deferenced so render value
@@ -307,6 +314,6 @@
old = len(value.get_references())
value.assembleRefs(self._base)
if old != len(value.get_references()):
- self._interpolate_inner(path, value, options)
+ self._interpolate_inner(path, options)
else:
raise InterpolationError('Bad reference count, path:' + repr(path))
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index c76b721..c1ef640 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -340,6 +340,12 @@
p1.interpolate()
self.assertEqual(p1.as_dict(), r)
+ def test_deep_refs_in_referenced_dicts(self):
+ p = Parameters({'A': '${C:a}', 'B': {'a': 1, 'b': 2}, 'C': '${B}'})
+ r = {'A': 1, 'B': {'a': 1, 'b': 2}, 'C': {'a': 1, 'b': 2}}
+ p.interpolate()
+ self.assertEqual(p.as_dict(), r)
+
def test_interpolate_escaping(self):
v = 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS)
d = {'foo': ESCAPE_CHARACTER + 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py
index db95e66..7395679 100644
--- a/reclass/utils/dictpath.py
+++ b/reclass/utils/dictpath.py
@@ -112,6 +112,15 @@
def _escape_string(self, string):
return string.replace(self._delim, '\\' + self._delim)
+ def has_ancestors(self):
+ return len(self._parts) > 1
+
+ def key_parts(self):
+ if self.has_ancestors():
+ return self._parts[::len(self._parts)-1]
+ else:
+ return []
+
def new_subpath(self, key):
try:
return DictPath(self._delim, self._parts + [self._escape_string(key)])