when merging values add option to ignore missing references that are overwritten and never used
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index 719c828..07786c1 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -7,6 +7,8 @@
# Released under the terms of the Artistic Licence 2.0
#
+import copy
+
from reclass.settings import Settings
from reclass.datatypes import Parameters
from reclass.errors import InfiniteRecursionError, InterpolationError
@@ -19,6 +21,17 @@
SIMPLE = {'one': 1, 'two': 2, 'three': 3}
SETTINGS = Settings()
+class MockDevice(object):
+ def __init__(self):
+ self._text = ''
+
+ def write(self, s):
+ self._text += s
+ return
+
+ def text(self):
+ return self._text
+
class TestParameters(unittest.TestCase):
def _construct_mocked_params(self, iterable=None, settings=SETTINGS):
@@ -500,5 +513,50 @@
p1.interpolate()
self.assertEqual(error.exception.message, "-> \n Bad references, at gamma\n ${beta}")
+ def test_ignore_overwriten_missing_reference(self):
+ settings = copy.deepcopy(SETTINGS)
+ settings.ignore_overwritten_missing_references = True
+ p1 = Parameters({'alpha': '${beta}'}, settings, '')
+ p2 = Parameters({'alpha': '${gamma}'}, settings, '')
+ p3 = Parameters({'gamma': 3}, settings, '')
+ r1 = {'alpha': 3, 'gamma': 3}
+ p1.merge(p2)
+ p1.merge(p3)
+ err1 = "[WARNING] Reference '${beta}' undefined\n"
+ with mock.patch('sys.stderr', new=MockDevice()) as std_err:
+ p1.interpolate()
+ self.assertEqual(p1.as_dict(), r1)
+ self.assertEqual(std_err.text(), err1)
+
+ def test_ignore_overwriten_missing_reference_last_value(self):
+ # an error should be raised if the last reference to be merged
+ # is missing even if ignore_overwritten_missing_references is true
+ settings = copy.deepcopy(SETTINGS)
+ settings.ignore_overwritten_missing_references = True
+ p1 = Parameters({'alpha': '${gamma}'}, settings, '')
+ p2 = Parameters({'alpha': '${beta}'}, settings, '')
+ p3 = Parameters({'gamma': 3}, settings, '')
+ p1.merge(p2)
+ p1.merge(p3)
+ with self.assertRaises(InterpolationError) as error:
+ p1.interpolate()
+ self.assertEqual(error.exception.message, "-> \n Cannot resolve ${beta}, at alpha")
+
+ def test_ignore_overwriten_missing_reference_dict(self):
+ # setting ignore_overwritten_missing_references to true should
+ # not change the behaviour for dicts
+ settings = copy.deepcopy(SETTINGS)
+ settings.ignore_overwritten_missing_references = True
+ p1 = Parameters({'alpha': '${beta}'}, settings, '')
+ p2 = Parameters({'alpha': '${gamma}'}, settings, '')
+ p3 = Parameters({'gamma': {'one': 1, 'two': 2}}, settings, '')
+ err1 = "[WARNING] Reference '${beta}' undefined\n"
+ p1.merge(p2)
+ p1.merge(p3)
+ with self.assertRaises(InterpolationError) as error, mock.patch('sys.stderr', new=MockDevice()) as std_err:
+ p1.interpolate()
+ self.assertEqual(error.exception.message, "-> \n Cannot resolve ${beta}, at alpha")
+ self.assertEqual(std_err.text(), err1)
+
if __name__ == '__main__':
unittest.main()
diff --git a/reclass/defaults.py b/reclass/defaults.py
index 82f49b2..9fbb478 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -17,10 +17,13 @@
OPT_PRETTY_PRINT = True
OPT_NO_REFS = False
OPT_OUTPUT = 'yaml'
+
OPT_IGNORE_CLASS_NOTFOUND = False
OPT_IGNORE_CLASS_NOTFOUND_REGEXP = ['.*']
OPT_IGNORE_CLASS_NOTFOUND_WARNING = True
+OPT_IGNORE_OVERWRITTEN_MISSING_REFERENCES = False
+
OPT_ALLOW_SCALAR_OVER_DICT = False
OPT_ALLOW_SCALAR_OVER_LIST = False
OPT_ALLOW_LIST_OVER_SCALAR = False
diff --git a/reclass/settings.py b/reclass/settings.py
index 987e707..44078ad 100644
--- a/reclass/settings.py
+++ b/reclass/settings.py
@@ -26,6 +26,7 @@
self.ignore_class_notfound_warning = options.get('ignore_class_notfound_warning', OPT_IGNORE_CLASS_NOTFOUND_WARNING)
+ self.ignore_overwritten_missing_references = options.get('ignore_overwritten_missing_references', OPT_IGNORE_OVERWRITTEN_MISSING_REFERENCES)
self.ref_parser = reclass.values.parser_funcs.get_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
self.simple_ref_parser = reclass.values.parser_funcs.get_simple_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
diff --git a/reclass/values/compitem.py b/reclass/values/compitem.py
index 5786934..2134ea8 100644
--- a/reclass/values/compitem.py
+++ b/reclass/values/compitem.py
@@ -49,3 +49,6 @@
def __repr__(self):
return 'CompItem(%r)' % self._items
+
+ def __str__(self):
+ return ''.join([ str(i) for i in self._items ])
diff --git a/reclass/values/refitem.py b/reclass/values/refitem.py
index ebb9708..0ae65e6 100644
--- a/reclass/values/refitem.py
+++ b/reclass/values/refitem.py
@@ -5,10 +5,12 @@
#
from item import Item
+from reclass.defaults import REFERENCE_SENTINELS
from reclass.settings import Settings
from reclass.utils.dictpath import DictPath
from reclass.errors import ResolveError
+
class RefItem(Item):
def __init__(self, items, settings):
@@ -62,3 +64,7 @@
def __repr__(self):
return 'RefItem(%r)' % self._items
+
+ def __str__(self):
+ strings = [ str(i) for i in self._items ]
+ return '{0}{1}{2}'.format(REFERENCE_SENTINELS[0], ''.join(strings), REFERENCE_SENTINELS[1])
diff --git a/reclass/values/scaitem.py b/reclass/values/scaitem.py
index df574d9..6fd3194 100644
--- a/reclass/values/scaitem.py
+++ b/reclass/values/scaitem.py
@@ -37,3 +37,6 @@
def __repr__(self):
return 'ScaItem({0!r})'.format(self._value)
+
+ def __str__(self):
+ return str(self._value)
diff --git a/reclass/values/value.py b/reclass/values/value.py
index cbd5ce2..4ec6051 100644
--- a/reclass/values/value.py
+++ b/reclass/values/value.py
@@ -83,3 +83,6 @@
def __repr__(self):
return 'Value(%r)' % self._item
+
+ def __str__(self):
+ return str(self._item)
diff --git a/reclass/values/valuelist.py b/reclass/values/valuelist.py
index f89a0c3..6201564 100644
--- a/reclass/values/valuelist.py
+++ b/reclass/values/valuelist.py
@@ -5,6 +5,9 @@
#
import copy
+import sys
+
+from reclass.errors import ResolveError
class ValueList(object):
@@ -87,12 +90,22 @@
output = None
deepCopied = False
+ last_error = None
for n, value in enumerate(self._values):
+ try:
+ new = value.render(context, inventory)
+ except ResolveError as e:
+ if self._settings.ignore_overwritten_missing_references and not isinstance(output, (dict, list)) and n != (len(self._values)-1):
+ new = None
+ last_error = e
+ print >>sys.stderr, "[WARNING] Reference '%s' undefined" % (str(value))
+ else:
+ raise e
+
if output is None:
- output = self._values[n].render(context, inventory)
+ output = new
deepCopied = False
else:
- new = value.render(context, inventory)
if isinstance(output, dict) and isinstance(new, dict):
p1 = Parameters(output, self._settings, None)
p2 = Parameters(new, self._settings, None)
@@ -109,6 +122,10 @@
raise TypeError('Cannot merge %s over %s' % (repr(self._values[n]), repr(self._values[n-1])))
else:
output = new
+
+ if isinstance(output, (dict, list)) and last_error is not None:
+ raise last_error
+
return output
def __repr__(self):