diff --git a/odml/property.py b/odml/property.py index e7084223..12650b98 100644 --- a/odml/property.py +++ b/odml/property.py @@ -772,6 +772,32 @@ def get_terminology_equivalent(self): except KeyError: return None + def _reorder(self, childlist, new_index): + lst = childlist + old_index = lst.index(self) + + # 2 cases: insert after old_index / insert before + if new_index > old_index: + new_index += 1 + lst.insert(new_index, self) + if new_index < old_index: + del lst[old_index + 1] + else: + del lst[old_index] + return old_index + + def reorder(self, new_index): + """ + Move this object in its parent child-list to the position *new_index*. + + :return: The old index at which the object was found. + """ + if not self.parent: + raise ValueError("odml.Property.reorder: " + "Property has no parent, cannot reorder in parent list.") + + return self._reorder(self.parent.properties, new_index) + def extend(self, obj, strict=True): """ Extend the list of values stored in this property by the passed values. Method @@ -857,6 +883,58 @@ def append(self, obj, strict=True): self._values.append(dtypes.get(new_value[0], self.dtype)) + def insert(self, index, obj, strict=True): + """ + Insert a single value to the list of stored values. Method will raise + a ValueError if the passed value cannot be converted to the current dtype. + + :param obj: the additional value. + :param index: position of the new value + :param strict: a Bool that controls whether dtypes must match. Default is True. + """ + + # Ignore empty values before nasty stuff happens, but make sure + # 0 and False get through. + if obj in [None, "", [], {}]: + return + + if not self.values: + self.values = obj + return + + new_value = self._convert_value_input(obj) + if len(new_value) > 1: + raise ValueError("odml.property.insert: Use extend to add a list of values!") + + new_value = self._convert_value_input(obj) + if len(new_value) > 1: + raise ValueError("odml.property.insert: Use extend to add a list of values!") + + if self._dtype.endswith("-tuple"): + t_count = int(self._dtype.split("-")[0]) + new_value = odml_tuple_import(t_count, new_value) + + if len(new_value) > 0 and strict and \ + dtypes.infer_dtype(new_value[0]) != self.dtype: + + type_check = dtypes.infer_dtype(new_value[0]) + if not (type_check == "string" and self.dtype in dtypes.special_dtypes) \ + and not self.dtype.endswith("-tuple"): + msg = "odml.Property.insert: passed value data type found " + msg += "(\"%s\") does not match expected dtype \"%s\"!" % (type_check, + self._dtype) + raise ValueError(msg) + + if not self._validate_values(new_value): + raise ValueError("odml.Property.insert: passed value(s) cannot be converted " + "to data type \'%s\'!" % self._dtype) + + if index > len(self._values): + warnings.warn("odml.Property.insert: Index %i larger than length of property.values. " + "Added value at end of list." % index, stacklevel=2) + + self._values.insert(index, dtypes.get(new_value[0], self.dtype)) + def pprint(self, indent=2, max_length=80, current_depth=-1): """ Pretty print method to visualize Properties and Section-Property trees. diff --git a/test/test_property.py b/test/test_property.py index 8a38daf6..57d624a2 100644 --- a/test/test_property.py +++ b/test/test_property.py @@ -1,4 +1,5 @@ import unittest +import datetime from odml import Property, Section, Document, DType from odml.property import BaseProperty @@ -245,6 +246,45 @@ def test_value_append(self): self.assertEqual(len(prop9), 3) self.assertRaises(ValueError, prop9.append, [[10, 11]]) + prop10 = Property(name="prop10", dtype="date", values=['2011-12-01', '2011-12-02']) + with self.assertRaises(ValueError): + prop10.append('2011-12-03', strict=True) + self.assertEqual(prop10.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2)]) + prop10.append('2011-12-03', strict=False) + self.assertEqual(prop10.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2), + datetime.date(2011, 12, 3)]) + prop10.append([datetime.date(2011, 12, 4)], strict=True) + self.assertEqual(prop10.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2), + datetime.date(2011, 12, 3), datetime.date(2011, 12, 4)]) + + + prop11 = Property(name="prop11", dtype="time", values=['12:00:01', '12:00:02']) + with self.assertRaises(ValueError): + prop11.append('12:00:03', strict=True) + self.assertEqual(prop11.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2)]) + prop11.append('12:00:03', strict=False) + self.assertEqual(prop11.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2), + datetime.time(12, 0, 3)]) + prop11.append([datetime.time(12, 0, 4)], strict=True) + self.assertEqual(prop11.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2), + datetime.time(12, 0, 3), datetime.time(12, 0, 4)]) + + prop12 = Property(name="prop12", dtype="datetime", + values=['2011-12-01 12:00:01', '2011-12-01 12:00:02']) + with self.assertRaises(ValueError): + prop12.append('2011-12-01 12:00:03', strict=True) + self.assertEqual(prop12.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2)]) + prop12.append('2011-12-01 12:00:03', strict=False) + self.assertEqual(prop12.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2), + datetime.datetime(2011, 12, 1, 12, 0, 3)]) + prop12.append([datetime.datetime(2011, 12, 1, 12, 0, 4)], strict=True) + self.assertEqual(prop12.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2), + datetime.datetime(2011, 12, 1, 12, 0, 3), + datetime.datetime(2011, 12, 1, 12, 0, 4)]) + def test_value_extend(self): prop = Property(name="extend") @@ -353,6 +393,215 @@ def test_value_extend(self): self.assertEqual(len(prop4), 5) self.assertRaises(ValueError, prop4.extend, [[10, 11]]) + prop4 = Property(name="prop4", dtype="date", values=['2011-12-01', '2011-12-02']) + with self.assertRaises(ValueError): + prop4.extend('2011-12-03', strict=True) + self.assertEqual(prop4.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2)]) + prop4.extend('2011-12-03', strict=False) + self.assertEqual(prop4.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2), + datetime.date(2011, 12, 3)]) + prop4.extend([datetime.date(2011, 12, 4)], strict=True) + self.assertEqual(prop4.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2), + datetime.date(2011, 12, 3), datetime.date(2011, 12, 4)]) + prop4.extend([datetime.date(2011, 12, 5), datetime.date(2011, 12, 6)], strict=True) + self.assertEqual(prop4.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2), + datetime.date(2011, 12, 3), datetime.date(2011, 12, 4), + datetime.date(2011, 12, 5), datetime.date(2011, 12, 6)]) + with self.assertRaises(ValueError): + prop4.extend(['2011-12-03', 'abc'], strict=False) + + + prop5 = Property(name="prop5", dtype="time", values=['12:00:01', '12:00:02']) + with self.assertRaises(ValueError): + prop5.extend('12:00:03', strict=True) + self.assertEqual(prop5.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2)]) + prop5.extend('12:00:03', strict=False) + self.assertEqual(prop5.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2), + datetime.time(12, 0, 3)]) + prop5.extend([datetime.time(12, 0, 4)], strict=True) + self.assertEqual(prop5.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2), + datetime.time(12, 0, 3), datetime.time(12, 0, 4)]) + prop5.extend([datetime.time(12, 0, 5), datetime.time(12, 0, 6)], strict=True) + self.assertEqual(prop5.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2), + datetime.time(12, 0, 3), datetime.time(12, 0, 4), + datetime.time(12, 0, 5), datetime.time(12, 0, 6)]) + with self.assertRaises(ValueError): + prop4.extend(['12:00:07', 'abc'], strict=False) + + prop6 = Property(name="prop6", dtype="datetime", + values=['2011-12-01 12:00:01', '2011-12-01 12:00:02']) + with self.assertRaises(ValueError): + prop6.extend('2011-12-01 12:00:03', strict=True) + self.assertEqual(prop6.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2)]) + prop6.extend('2011-12-01 12:00:03', strict=False) + self.assertEqual(prop6.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2), + datetime.datetime(2011, 12, 1, 12, 0, 3)]) + prop6.extend([datetime.datetime(2011, 12, 1, 12, 0, 4)], strict=True) + self.assertEqual(prop6.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2), + datetime.datetime(2011, 12, 1, 12, 0, 3), + datetime.datetime(2011, 12, 1, 12, 0, 4)]) + prop6.extend([datetime.datetime(2011, 12, 1, 12, 0, 5), + datetime.datetime(2011, 12, 1, 12, 0, 6)], strict=True) + self.assertEqual(prop6.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2), + datetime.datetime(2011, 12, 1, 12, 0, 3), + datetime.datetime(2011, 12, 1, 12, 0, 4), + datetime.datetime(2011, 12, 1, 12, 0, 5), + datetime.datetime(2011, 12, 1, 12, 0, 6)]) + with self.assertRaises(ValueError): + prop4.extend(['2011-12-03 12:00:07', 'abc'], strict=False) + + def test_insert(self): + prop1 = Property(name="prop1", dtype="int", values=[0,2]) + prop1.insert(1, 1) + self.assertEqual(prop1.values, [0, 1, 2]) + prop1.insert(4, 3) + self.assertEqual(prop1.values, [0, 1, 2, 3]) + + with self.assertRaises(ValueError): + prop1.append([4, 5]) + self.assertEqual(prop1.values, [0, 1, 2, 3]) + + with self.assertRaises(ValueError): + prop1.insert(1, 3.14) + with self.assertRaises(ValueError): + prop1.insert(1, True) + with self.assertRaises(ValueError): + prop1.insert(1, "5.927") + self.assertEqual(prop1.values, [0, 1, 2, 3]) + + prop2 = Property(name="prop2", dtype="int", values=[1, 2]) + prop2.insert(1, 3.14, strict=False) + self.assertEqual(prop2.values, [1, 3, 2]) + prop2.insert(1, True, strict=False) + self.assertEqual(prop2.values, [1, 1, 3, 2]) + prop2.insert(1, "5.927", strict=False) + self.assertEqual(prop2.values, [1, 5, 1, 3, 2]) + + prop3 = Property(name="prop3", dtype="string", values=["a", "c"]) + prop3.insert(1, "b") + self.assertEqual(prop3.values, ["a", "b", "c"]) + prop3.insert(1, 1, strict=False) + self.assertEqual(prop3.values, ["a", "1", "b", "c"]) + with self.assertRaises(ValueError): + prop3.insert(1, 2, strict=True) + self.assertEqual(prop3.values, ["a", "1", "b", "c"]) + + prop4 = Property(name="prop4", dtype="float", values=[1.1, 1.3]) + prop4.insert(1, 1.2) + self.assertEqual(prop4.values, [1.1, 1.2, 1.3]) + prop4.insert(1, 2, strict=False) + self.assertEqual(prop4.values, [1.1, 2.0, 1.2, 1.3]) + with self.assertRaises(ValueError): + prop4.insert(1, 2, strict=True) + self.assertEqual(prop4.values, [1.1, 2.0, 1.2, 1.3]) + + prop5 = Property(name="prop5", dtype="2-tuple", values=["(1; 2)", "(5; 6)"]) + prop5.insert(1, "(3; 4)", strict=True) + self.assertEqual(prop5.values, [['1', '2'], ['3', '4'], ['5', '6']]) + prop5.insert(1, [['4', '5']], strict=True) + self.assertEqual(prop5.values, [['1', '2'], ['4', '5'], ['3', '4'], ['5', '6']]) + + prop6 = Property(name="prop6", dtype="boolean", values=[True, True]) + prop6.insert(1, False) + self.assertEqual(prop6.values, [True, False, True]) + prop6.insert(1, 1, strict=False) + self.assertEqual(prop6.values, [True, True, False, True]) + with self.assertRaises(ValueError): + prop6.insert(1, 2, strict=False) + self.assertEqual(prop6.values, [True, True, False, True]) + with self.assertRaises(ValueError): + prop6.insert(1, 0, strict=True) + self.assertEqual(prop6.values, [True, True, False, True]) + + prop7 = Property(name="prop7", dtype="date", values=['2011-12-01', '2011-12-04']) + with self.assertRaises(ValueError): + prop7.insert(1, '2011-12-03', strict=True) + self.assertEqual(prop7.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 4)]) + prop7.insert(1, '2011-12-03', strict=False) + self.assertEqual(prop7.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 3), + datetime.date(2011, 12, 4)]) + prop7.insert(1, [datetime.date(2011, 12, 2)], strict=True) + self.assertEqual(prop7.values, [datetime.date(2011, 12, 1), datetime.date(2011, 12, 2), + datetime.date(2011, 12, 3), datetime.date(2011, 12, 4)]) + + + prop8 = Property(name="prop8", dtype="time", values=['12:00:01', '12:00:04']) + with self.assertRaises(ValueError): + prop8.insert(1, '12:00:03', strict=True) + self.assertEqual(prop8.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 4)]) + prop8.insert(1, '12:00:03', strict=False) + self.assertEqual(prop8.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 3), + datetime.time(12, 0, 4)]) + prop8.insert(1, [datetime.time(12, 0, 2)], strict=True) + self.assertEqual(prop8.values, [datetime.time(12, 0, 1), datetime.time(12, 0, 2), + datetime.time(12, 0, 3), datetime.time(12, 0, 4)]) + + prop9 = Property(name="prop9", dtype="datetime", + values=['2011-12-01 12:00:01', '2011-12-01 12:00:04']) + with self.assertRaises(ValueError): + prop9.insert(1, '2011-12-01 12:00:03', strict=True) + self.assertEqual(prop9.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 4)]) + prop9.insert(1, '2011-12-01 12:00:03', strict=False) + self.assertEqual(prop9.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 3), + datetime.datetime(2011, 12, 1, 12, 0, 4)]) + prop9.insert(1, [datetime.datetime(2011, 12, 1, 12, 0, 2)], strict=True) + self.assertEqual(prop9.values, [datetime.datetime(2011, 12, 1, 12, 0, 1), + datetime.datetime(2011, 12, 1, 12, 0, 2), + datetime.datetime(2011, 12, 1, 12, 0, 3), + datetime.datetime(2011, 12, 1, 12, 0, 4)]) + + prop10 = Property(name="prop", value=["Earth is\n No. 3."], dtype=DType.text) + prop10.insert(1, "Mars is\n No. 4.", strict=False) + self.assertEqual(len(prop10), 2) + self.assertEqual(prop10.values, ["Earth is\n No. 3.", "Mars is\n No. 4."]) + prop10.insert(1, 'A new world emerged?', strict=True) + self.assertEqual(prop10.values, ["Earth is\n No. 3.", + "A new world emerged?", + "Mars is\n No. 4."]) + prop10.insert(1, 1, strict=False) + self.assertEqual(prop10.values, ["Earth is\n No. 3.", "1", + "A new world emerged?", + "Mars is\n No. 4."]) + with self.assertRaises(ValueError): + prop10.insert(1, 1, strict=True) + self.assertEqual(prop10.values, ["Earth is\n No. 3.", "1", + "A new world emerged?", + "Mars is\n No. 4."]) + + def test_reorder(self): + sec = Section() + prop_zero = Property(name="prop_zero", parent=sec) + prop_one = Property(name="prop_one", parent=sec) + prop_two = Property(name="prop_two", parent=sec) + prop_three = Property(name="prop_three", parent=sec) + + self.assertEqual(sec.properties[0].name, prop_zero.name) + self.assertEqual(sec.properties[2].name, prop_two.name) + prop_two.reorder(0) + + self.assertEqual(sec.properties[0].name, prop_two.name) + self.assertEqual(sec.properties[1].name, prop_zero.name) + self.assertEqual(sec.properties[2].name, prop_one.name) + self.assertEqual(sec.properties[3].name, prop_three.name) + + prop_two.reorder(2) + + self.assertEqual(sec.properties[0].name, prop_zero.name) + self.assertEqual(sec.properties[1].name, prop_one.name) + self.assertEqual(sec.properties[2].name, prop_two.name) + self.assertEqual(sec.properties[3].name, prop_three.name) + + # Test Exception on unconnected property + prop = Property(name="main") + with self.assertRaises(ValueError): + prop.reorder(0) + def test_get_set_value(self): values = [1, 2, 3, 4, 5] prop = Property("property", value=values)