refactor out an Exports class as sub class of Parameters
diff --git a/reclass/cli.py b/reclass/cli.py
index 17467cd..7083917 100644
--- a/reclass/cli.py
+++ b/reclass/cli.py
@@ -30,6 +30,7 @@
storage = get_storage(options.storage_type, options.nodes_uri,
options.classes_uri, options.exports_uri,
default_environment='base')
+
class_mappings = defaults.get('class_mappings')
reclass = Core(storage, class_mappings)
diff --git a/reclass/core.py b/reclass/core.py
index 1495a4c..4d5931a 100644
--- a/reclass/core.py
+++ b/reclass/core.py
@@ -17,7 +17,7 @@
import string
import yaml
from reclass.output.yaml_outputter import ExplicitDumper
-from reclass.datatypes import Entity, Classes, Parameters
+from reclass.datatypes import Entity, Classes, Parameters, Exports
from reclass.errors import MappingFormatError, ClassNotFound
from reclass.defaults import AUTOMATIC_RECLASS_PARAMETERS
@@ -120,7 +120,6 @@
else:
return Parameters()
-
def _nodeinfo(self, nodename, exports):
node_entity = self._storage.get_node(nodename)
base_entity = Entity(name='base')
@@ -155,7 +154,7 @@
return False
def nodeinfo(self, nodename):
- original_exports = Parameters(self._storage.get_exports())
+ original_exports = Exports(self._storage.get_exports())
exports = copy.deepcopy(original_exports)
original_exports.render_simple()
ret = self._nodeinfo_as_dict(nodename, self._nodeinfo(nodename, exports))
@@ -163,7 +162,7 @@
return ret
def inventory(self):
- original_exports = Parameters(self._storage.get_exports())
+ original_exports = Exports(self._storage.get_exports())
exports = copy.deepcopy(original_exports)
original_exports.render_simple()
nodes = { key for key in exports.as_dict()}
diff --git a/reclass/datatypes/__init__.py b/reclass/datatypes/__init__.py
index 20f7551..b506de0 100644
--- a/reclass/datatypes/__init__.py
+++ b/reclass/datatypes/__init__.py
@@ -9,4 +9,5 @@
from applications import Applications
from classes import Classes
from entity import Entity
+from exports import Exports
from parameters import Parameters
diff --git a/reclass/datatypes/entity.py b/reclass/datatypes/entity.py
index 60c15df..f73c137 100644
--- a/reclass/datatypes/entity.py
+++ b/reclass/datatypes/entity.py
@@ -8,6 +8,7 @@
#
from classes import Classes
from applications import Applications
+from exports import Exports
from parameters import Parameters
class Entity(object):
@@ -23,7 +24,7 @@
if applications is None: applications = Applications()
self._set_applications(applications)
if parameters is None: parameters = Parameters()
- if exports is None: exports = Parameters()
+ if exports is None: exports = Exports()
self._set_parameters(parameters)
self._set_exports(exports)
self._uri = uri or ''
@@ -58,7 +59,7 @@
self._parameters = parameters
def _set_exports(self, exports):
- if not isinstance(exports, Parameters):
+ if not isinstance(exports, Exports):
raise TypeError('Entity.exports cannot be set to '\
'instance of type %s' % type(exports))
self._exports = exports
diff --git a/reclass/datatypes/exports.py b/reclass/datatypes/exports.py
new file mode 100644
index 0000000..ea81912
--- /dev/null
+++ b/reclass/datatypes/exports.py
@@ -0,0 +1,34 @@
+#
+# -*- coding: utf-8 -*-
+#
+# This file is part of reclass (http://github.com/madduck/reclass)
+#
+from parameters import Parameters
+
+class Exports(Parameters):
+
+ def __init__(self, mapping=None, delimiter=None):
+ super(Exports, self).__init__(mapping, delimiter)
+
+ def __repr__(self):
+ return '%s(%r, %r)' % (self.__class__.__name__, self._base,
+ self.delimiter)
+
+ def delete_key(self, key):
+ self._base.pop(key, None)
+ self._unrendered.pop(key, None)
+
+ def overwrite(self, other):
+ overdict = {'~' + key: value for key, value in other.iteritems()}
+ self.merge(overdict)
+
+ def interpolate_from_external(self, external, options=None):
+ self._initialise_interpolate(options)
+ external._initialise_interpolate(options)
+ while len(self._unrendered) > 0:
+ path, v = self._unrendered.iteritems().next()
+ value = path.get_value(self._base)
+ external._interpolate_references(path, value, None)
+ new = value.render(external._base, self._options)
+ path.set_value(self._base, new)
+ del self._unrendered[path]
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index 6d2f334..fc6448b 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -54,9 +54,9 @@
self._options = None
if mapping is not None:
# we initialise by merging
- self._initmerge = True
+ self._keep_overrides = True
self.merge(mapping)
- self._initmerge = False
+ self._keep_overrides = False
delimiter = property(lambda self: self._delimiter)
@@ -65,7 +65,7 @@
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self._base,
- self.delimiter)
+ self._delimiter)
def __eq__(self, other):
return isinstance(other, type(self)) \
@@ -75,10 +75,6 @@
def __ne__(self, other):
return not self.__eq__(other)
- def delete_key(self, key):
- self._base.pop(key, None)
- self._unrendered.pop(key, None)
-
def as_dict(self):
return self._base.copy()
@@ -137,24 +133,10 @@
"""
- if isinstance(cur, dict):
- ret = cur
- else:
- # nothing sensible to do
- raise TypeError('Cannot merge dict into {0} '
- 'objects'.format(type(cur)))
-
- if self.delimiter is None:
- # a delimiter of None indicates that there is no value
- # processing to be done, and since there is no current
- # value, we do not need to walk the new dictionary:
- ret.update(new)
- return ret
-
+ ret = cur
ovrprfx = Parameters.DICT_KEY_OVERRIDE_PREFIX
-
for key, newvalue in new.iteritems():
- if key.startswith(ovrprfx) and not self._initmerge:
+ if key.startswith(ovrprfx) and not self._keep_overrides:
ret[key.lstrip(ovrprfx)] = newvalue
else:
ret[key] = self._merge_recurse(ret.get(key), newvalue, path.new_subpath(key))
@@ -180,7 +162,7 @@
"""
- if isinstance(new, dict) and (cur is None or isinstance(cur, (dict))):
+ if isinstance(new, dict) and (cur is None or isinstance(cur, dict)):
if cur is None:
cur = {}
return self._merge_dict(cur, new, path)
@@ -206,19 +188,15 @@
if isinstance(other, dict):
wrapped = copy.deepcopy(other)
self._wrap_dict(wrapped)
- self._base = self._merge_recurse(self._base, wrapped, DictPath(self.delimiter))
+ self._base = self._merge_recurse(self._base, wrapped, DictPath(self._delimiter))
elif isinstance(other, self.__class__):
- self._base = self._merge_recurse(self._base, other._base, DictPath(self.delimiter))
+ self._base = self._merge_recurse(self._base, other._base, DictPath(self._delimiter))
else:
raise TypeError('Cannot merge %s objects into %s' % (type(other),
self.__class__.__name__))
- def overwrite(self, other):
- overdict = {'~' + key: value for key, value in other.iteritems()}
- self.merge(overdict)
-
def render_simple(self, options=None):
self._unrendered = None
self._initialise_interpolate(options)
@@ -252,27 +230,6 @@
for n, value in enumerate(item_list):
self._render_simple_container(item_list, n, value, path)
- def _initialise_interpolate(self, options):
- if options is None:
- self._options = MergeOptions()
- else:
- self._options = options
-
- if self._unrendered is None:
- self._unrendered = {}
- self._render_simple_dict(self._base, DictPath(self.delimiter))
-
- def interpolate_from_external(self, external, options=None):
- self._initialise_interpolate(options)
- external._initialise_interpolate(options)
- while len(self._unrendered) > 0:
- path, v = self._unrendered.iteritems().next()
- value = path.get_value(self._base)
- external._interpolate_references(path, value, None)
- new = value.render(external._base, self._options)
- path.set_value(self._base, new)
- del self._unrendered[path]
-
def interpolate(self, exports=None, options=None):
self._initialise_interpolate(options)
while len(self._unrendered) > 0:
@@ -282,6 +239,16 @@
path, v = self._unrendered.iteritems().next()
self._interpolate_inner(path, exports)
+ def _initialise_interpolate(self, options):
+ if options is None:
+ self._options = MergeOptions()
+ else:
+ self._options = options
+
+ if self._unrendered is None:
+ self._unrendered = {}
+ self._render_simple_dict(self._base, DictPath(self._delimiter))
+
def _interpolate_inner(self, path, exports):
value = path.get_value(self._base)
if not isinstance(value, (Value, ValueList)):
@@ -312,7 +279,7 @@
all_refs = False
while not all_refs:
for ref in value.get_references():
- path_from_ref = DictPath(self.delimiter, ref)
+ path_from_ref = DictPath(self._delimiter, ref)
if path_from_ref in self._unrendered:
if self._unrendered[path_from_ref] is False:
@@ -326,7 +293,7 @@
self._interpolate_inner(path_from_ref, exports)
else:
# ensure ancestor keys are already dereferenced
- ancestor = DictPath(self.delimiter)
+ ancestor = DictPath(self._delimiter)
for k in path_from_ref.key_parts():
ancestor = ancestor.new_subpath(k)
if ancestor in self._unrendered:
diff --git a/reclass/datatypes/tests/test_entity.py b/reclass/datatypes/tests/test_entity.py
index c583bfd..76b90b9 100644
--- a/reclass/datatypes/tests/test_entity.py
+++ b/reclass/datatypes/tests/test_entity.py
@@ -6,7 +6,7 @@
# Copyright © 2007–14 martin f. krafft <madduck@madduck.net>
# Released under the terms of the Artistic Licence 2.0
#
-from reclass.datatypes import Entity, Classes, Parameters, Applications
+from reclass.datatypes import Entity, Classes, Parameters, Applications, Exports
import unittest
try:
import unittest.mock as mock
@@ -14,11 +14,12 @@
import mock
@mock.patch.multiple('reclass.datatypes', autospec=True, Classes=mock.DEFAULT,
- Applications=mock.DEFAULT, Parameters=mock.DEFAULT)
+ Applications=mock.DEFAULT, Parameters=mock.DEFAULT,
+ Exports=mock.DEFAULT)
class TestEntity(unittest.TestCase):
- def _make_instances(self, Classes, Applications, Parameters):
- return Classes(), Applications(), Parameters(), mock.MagicMock(spec=Parameters)
+ def _make_instances(self, Classes, Applications, Parameters, Exports):
+ return Classes(), Applications(), Parameters(), Exports()
def test_constructor_default(self, **mocks):
# Actually test the real objects by calling the default constructor,
@@ -29,7 +30,7 @@
self.assertIsInstance(e.classes, Classes)
self.assertIsInstance(e.applications, Applications)
self.assertIsInstance(e.parameters, Parameters)
- self.assertIsInstance(e.exports, Parameters)
+ self.assertIsInstance(e.exports, Exports)
def test_constructor_empty(self, **types):
instances = self._make_instances(**types)
@@ -157,9 +158,9 @@
class TestEntityNoMock(unittest.TestCase):
def test_exports_with_refs(self):
- exports = Parameters({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
+ exports = Exports({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
exports_entity = Entity(None, None, None, exports)
- node3_exports = Parameters({'a': '${a}', 'b': '${b}'})
+ node3_exports = Exports({'a': '${a}', 'b': '${b}'})
node3_parameters = Parameters({'name': 'node3', 'a': '${c}', 'b': 5})
node3_parameters.merge({'c': 3})
node3_entity = Entity(None, None, node3_parameters, node3_exports)
@@ -168,9 +169,9 @@
self.assertDictEqual(exports.as_dict(), r)
def test_reference_to_an_export(self):
- exports = Parameters({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
+ exports = Exports({'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}})
exports_entity = Entity(None, None, None, exports)
- node3_exports = Parameters({'a': '${a}', 'b': '${b}'})
+ node3_exports = Exports({'a': '${a}', 'b': '${b}'})
node3_parameters = Parameters({'name': 'node3', 'ref': '${exp}', 'a': '${c}', 'b': 5})
node3_parameters.merge({'c': 3, 'exp': '$[ exports:a ]'})
node3_entity = Entity(None, None, node3_parameters, node3_exports)
@@ -179,9 +180,9 @@
self.assertDictEqual(exports.as_dict(), r)
def test_exports_with_nested_references(self):
- exports = Parameters({'node1': {'alpha': {'a': 1, 'b': 2}}, 'node2': {'alpha': {'a': 3, 'b': 4}}})
+ exports = Exports({'node1': {'alpha': {'a': 1, 'b': 2}}, 'node2': {'alpha': {'a': 3, 'b': 4}}})
exports_entity = Entity(None, None, None, exports)
- node3_exports = Parameters({'alpha': '${alpha}'})
+ node3_exports = Exports({'alpha': '${alpha}'})
node3_parameters = Parameters({'name': 'node3', 'alpha': {'a': '${one}', 'b': '${two}'}, 'beta': '$[ exports:alpha ]', 'one': '111', 'two': '${three}', 'three': '123'})
node3_entity = Entity(None, None, node3_parameters, node3_exports)
res_params = {'beta': {'node1': {'a': 1, 'b': 2}, 'node3': {'a': '111', 'b': '123'}, 'node2': {'a': 3, 'b': 4}}, 'name': 'node3', 'alpha': {'a': '111', 'b': '123'}, 'three': '123', 'two': '123', 'one': '111'}
diff --git a/reclass/datatypes/tests/test_exports.py b/reclass/datatypes/tests/test_exports.py
new file mode 100644
index 0000000..f1b960b
--- /dev/null
+++ b/reclass/datatypes/tests/test_exports.py
@@ -0,0 +1,45 @@
+#
+# -*- coding: utf-8 -*-
+#
+# This file is part of reclass (http://github.com/madduck/reclass)
+#
+
+from reclass.datatypes import Exports, Parameters
+import unittest
+try:
+ import unittest.mock as mock
+except ImportError:
+ import mock
+
+class TestExportsNoMock(unittest.TestCase):
+
+ def test_overwrite_method(self):
+ e = Exports({'alpha': { 'one': 1, 'two': 2}})
+ d = {'alpha': { 'three': 3, 'four': 4}}
+ e.overwrite(d)
+ e.render_simple()
+ self.assertEqual(e.as_dict(), d)
+
+ def test_value_expr_exports(self):
+ e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
+ p = Parameters({'exp': '$[ exports:a ]'})
+ r = {'exp': {'node1': 1, 'node2': 3}}
+ p.interpolate(e)
+ self.assertEqual(p.as_dict(), r)
+
+ def test_if_expr_exports(self):
+ e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
+ p = Parameters({'exp': '$[ exports:a if exports:b == 4 ]'})
+ r = {'exp': {'node2': 3}}
+ p.interpolate(e)
+ self.assertEqual(p.as_dict(), r)
+
+ def test_if_expr_exports_with_refs(self):
+ e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
+ p = Parameters({'exp': '$[ exports:a if exports:b == self:test_value ]', 'test_value': 2})
+ r = {'exp': {'node1': 1}, 'test_value': 2}
+ p.interpolate(e)
+ self.assertEqual(p.as_dict(), r)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index 093c28c..1cc3804 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -401,33 +401,5 @@
p1.interpolate()
self.assertEqual(p1.as_dict(), r)
- def test_overwrite_method(self):
- p = Parameters({'alpha': { 'one': 1, 'two': 2}})
- d = {'alpha': { 'three': 3, 'four': 4}}
- p.overwrite(d)
- p.render_simple()
- self.assertEqual(p.as_dict(), d)
-
- def test_value_expr_exports(self):
- e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
- p = Parameters({'exp': '$[ exports:a ]'})
- r = {'exp': {'node1': 1, 'node2': 3}}
- p.interpolate(e)
- self.assertEqual(p.as_dict(), r)
-
- def test_if_expr_exports(self):
- e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
- p = Parameters({'exp': '$[ exports:a if exports:b == 4 ]'})
- r = {'exp': {'node2': 3}}
- p.interpolate(e)
- self.assertEqual(p.as_dict(), r)
-
- def test_if_expr_exports_with_refs(self):
- e = {'node1': {'a': 1, 'b': 2}, 'node2': {'a': 3, 'b': 4}}
- p = Parameters({'exp': '$[ exports:a if exports:b == self:test_value ]', 'test_value': 2})
- r = {'exp': {'node1': 1}, 'test_value': 2}
- p.interpolate(e)
- self.assertEqual(p.as_dict(), r)
-
if __name__ == '__main__':
unittest.main()
diff --git a/reclass/storage/yaml_fs/yamlfile.py b/reclass/storage/yaml_fs/yamlfile.py
index dcd6df4..ad262cd 100644
--- a/reclass/storage/yaml_fs/yamlfile.py
+++ b/reclass/storage/yaml_fs/yamlfile.py
@@ -53,7 +53,7 @@
exports = self._data.get('exports')
if exports is None:
exports = {}
- exports = datatypes.Parameters(exports)
+ exports = datatypes.Exports(exports)
if name is None:
name = self._path