Improve Entity class
Among a bit of cleanup, this mainly adds type-safety and a better
representation of instances.
Also, switch from nose to unittest.
Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/reclass/datatypes/entity.py b/reclass/datatypes/entity.py
index a643f3f..7135c71 100644
--- a/reclass/datatypes/entity.py
+++ b/reclass/datatypes/entity.py
@@ -7,45 +7,64 @@
# Released under the terms of the Artistic Licence 2.0
#
from classes import Classes
-from parameters import Parameters
from applications import Applications
+from parameters import Parameters
class Entity(object):
-
+ '''
+ A collection of Classes, Parameters, and Applications, mainly as a wrapper
+ for merging. The name of an Entity will be updated to the name of the
+ Entity that is being merged.
+ '''
def __init__(self, classes=None, applications=None, parameters=None,
name=None):
- if applications is None: applications = Applications()
- self._applications = applications
if classes is None: classes = Classes()
- self._classes = classes
+ self._set_classes(classes)
+ if applications is None: applications = Applications()
+ self._set_applications(applications)
if parameters is None: parameters = Parameters()
- self._parameters = parameters
- self._name = name
+ self._set_parameters(parameters)
+ self._name = name or ''
- applications = property(lambda self: self._applications)
- classes = property(lambda self: self._classes)
- parameters = property(lambda self: self._parameters)
- name = property(lambda self: self._name)
+ name = property(lambda s: s._name)
+ classes = property(lambda s: s._classes)
+ applications = property(lambda s: s._applications)
+ parameters = property(lambda s: s._parameters)
+
+ def _set_classes(self, classes):
+ if not isinstance(classes, Classes):
+ raise TypeError('Entity.classes cannot be set to '\
+ 'instance of type %s' % type(classes))
+ self._classes = classes
+
+ def _set_applications(self, applications):
+ if not isinstance(applications, Applications):
+ raise TypeError('Entity.applications cannot be set to '\
+ 'instance of type %s' % type(applications))
+ self._applications = applications
+
+ def _set_parameters(self, parameters):
+ if not isinstance(parameters, Parameters):
+ raise TypeError('Entity.parameters cannot be set to '\
+ 'instance of type %s' % type(parameters))
+ self._parameters = parameters
def merge(self, other):
- self.classes.merge(other.classes)
- self.applications.merge(other.applications)
- self.parameters.merge(other.parameters)
+ self._classes.merge(other._classes)
+ self._applications.merge(other._applications)
+ self._parameters.merge(other._parameters)
self._name = other.name
def __eq__(self, other):
- return self.applications == other.applications \
- and self.classes == other.classes \
- and self.parameters == other.parameters \
- and self.name == other.name
+ return self._applications == other._applications \
+ and self._classes == other._classes \
+ and self._parameters == other._parameters \
+ and self._name == other._name
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
- if self.name:
- name = " '%s'" % self.name
- else:
- name = ''
- return '<Entity{0} classes:{1} applications:{2}, parameters:{3}>'.format(
- name, len(self.classes), len(self.applications), len(self.parameters))
+ return "%s(%r, %r, %r, %r)" % (self.__class__.__name__,
+ self.classes, self.applications,
+ self.parameters, self.name)
diff --git a/reclass/datatypes/tests/test_entity.py b/reclass/datatypes/tests/test_entity.py
index 79a5274..d4f85b3 100644
--- a/reclass/datatypes/tests/test_entity.py
+++ b/reclass/datatypes/tests/test_entity.py
@@ -7,34 +7,95 @@
# Released under the terms of the Artistic Licence 2.0
#
from reclass.datatypes import Entity, Classes, Parameters, Applications
+import unittest
+try:
+ import unittest.mock as mock
+except ImportError:
+ import mock
-class TestEntity:
+@mock.patch.multiple('reclass.datatypes', autospec=True, Classes=mock.DEFAULT,
+ Applications=mock.DEFAULT,
+ Parameters=mock.DEFAULT)
+class TestEntity(unittest.TestCase):
- def test_constructor0(self):
+ def _make_instances(self, Classes, Applications, Parameters):
+ return Classes(), Applications(), Parameters()
+
+ def test_constructor_default(self, **mocks):
+ # Actually test the real objects by calling the default constructor,
+ # all other tests shall pass instances to the constructor
e = Entity()
- assert isinstance(e.classes, Classes)
- assert len(e.classes) == 0
- assert isinstance(e.parameters, Parameters)
- assert len(e.parameters) == 0
- assert isinstance(e.applications, Applications)
- assert len(e.applications) == 0
+ self.assertEqual(e.name, '')
+ self.assertIsInstance(e.classes, Classes)
+ self.assertIsInstance(e.applications, Applications)
+ self.assertIsInstance(e.parameters, Parameters)
- def test_constructor1(self):
- c = Classes(['one', 'two'])
- p = Parameters({'blue':'white', 'black':'yellow'})
- a = Applications(['three', 'four'])
- e = Entity(c, a, p)
- assert e.classes == c
- assert e.parameters == p
- assert e.applications == a
+ def test_constructor_empty(self, **types):
+ instances = self._make_instances(**types)
+ e = Entity(*instances)
+ self.assertEqual(e.name, '')
+ cl, al, pl = [getattr(i, '__len__') for i in instances]
+ self.assertEqual(len(e.classes), cl.return_value)
+ cl.assert_called_once_with()
+ self.assertEqual(len(e.applications), al.return_value)
+ al.assert_called_once_with()
+ self.assertEqual(len(e.parameters), pl.return_value)
+ pl.assert_called_once_with()
- def test_merge_to_empty(self):
- e1 = Entity()
- c = Classes(['one', 'two'])
- p = Parameters({'blue':'white', 'black':'yellow'})
- a = Applications(['three', 'four'])
- e2 = Entity(c, a, p)
+ def test_constructor_empty_named(self, **types):
+ name = 'empty'
+ e = Entity(*self._make_instances(**types), name=name)
+ self.assertEqual(e.name, name)
+
+ def test_equal_empty(self, **types):
+ instances = self._make_instances(**types)
+ self.assertEqual(Entity(*instances), Entity(*instances))
+ for i in instances:
+ i.__eq__.assert_called_once_with(i)
+
+ def test_equal_empty_named(self, **types):
+ instances = self._make_instances(**types)
+ self.assertEqual(Entity(*instances), Entity(*instances))
+ name = 'empty'
+ self.assertEqual(Entity(*instances, name=name),
+ Entity(*instances, name=name))
+
+ def test_unequal_empty_named(self, **types):
+ instances = self._make_instances(**types)
+ name = 'empty'
+ self.assertNotEqual(Entity(*instances, name='empty'),
+ Entity(*instances, name='ytpme'))
+ for i in instances:
+ i.__eq__.assert_called_once_with(i)
+
+ def _test_constructor_wrong_types(self, which_replace, **types):
+ instances = self._make_instances(**types)
+ instances[which_replace] = 'Invalid type'
+ e = Entity(*instances)
+
+ def test_constructor_wrong_type_classes(self, **types):
+ self.assertRaises(TypeError, self._test_constructor_wrong_types, 0)
+
+ def test_constructor_wrong_type_applications(self, **types):
+ self.assertRaises(TypeError, self._test_constructor_wrong_types, 1)
+
+ def test_constructor_wrong_type_parameters(self, **types):
+ self.assertRaises(TypeError, self._test_constructor_wrong_types, 2)
+
+ def test_merge(self, **types):
+ instances = self._make_instances(**types)
+ e = Entity(*instances)
+ e.merge(e)
+ for i, fn in zip(instances, ('merge_unique', 'merge_unique', 'merge')):
+ getattr(i, fn).assert_called_once_with(i)
+
+ def test_merge_newname(self, **types):
+ instances = self._make_instances(**types)
+ newname = 'newname'
+ e1 = Entity(*instances, name='oldname')
+ e2 = Entity(*instances, name=newname)
e1.merge(e2)
- assert e1.classes == e2.classes
- assert e1.applications == e2.applications
- assert e1.parameters == e2.parameters
+ self.assertEqual(e1.name, newname)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/storage/__init__.py b/reclass/storage/__init__.py
index 76c2a67..ac83c84 100644
--- a/reclass/storage/__init__.py
+++ b/reclass/storage/__init__.py
@@ -29,9 +29,9 @@
return {'__reclass__' : {'node': node, 'node_uri': uri,
'timestamp': _get_timestamp()
},
- 'classes': list(entity.classes),
- 'applications': list(entity.applications),
- 'parameters': dict(entity.parameters)
+ 'classes': entity.classes,
+ 'applications': entity.applications,
+ 'parameters': entity.parameters
}
def _list_inventory(self):