diff --git a/odml/base.py b/odml/base.py index a074e254..f28644d2 100644 --- a/odml/base.py +++ b/odml/base.py @@ -654,18 +654,31 @@ def get_repository(self): """ return self._repository - def create_section(self, name, type="undefined", oid=None): + def create_section(self, name=None, type="n.s.", oid=None, definition=None, + reference=None, repository=None, link=None, include=None): """ Creates a new subsection that is a child of this section. - :param name: The name of the section to create. - :param type: The type of the section. + :param name: The name of the section to create. If the name is not + provided, the object id of the Section is assigned as its name. + Section name is a required attribute. + :param type: String providing a grouping description for similar Sections. + Section type is a required attribute and will be set to the string + 'n.s.' by default. :param oid: object id, UUID string as specified in RFC 4122. If no id is provided, an id will be generated and assigned. + :param definition: String defining this Section. + :param reference: A reference (e.g. an URL) to an external definition + of the Section. + :param repository: URL to a repository in which the Section is defined. + :param link: Specifies a soft link, i.e. a path within the document. + :param include: Specifies an arbitrary URL. Can only be used if *link* is not set. + :return: The new section. """ from odml.section import BaseSection - sec = BaseSection(name=name, type=type, oid=oid) + sec = BaseSection(name=name, type=type, definition=definition, reference=reference, + repository=repository, link=link, include=include, oid=oid) sec.parent = self return sec diff --git a/odml/property.py b/odml/property.py index 08a69508..c3820ee7 100644 --- a/odml/property.py +++ b/odml/property.py @@ -140,7 +140,9 @@ def __init__(self, name=None, values=None, parent=None, unit=None, for err in validation.Validation(self).errors: if err.is_error: - msg = "\n\t- %s %s: %s" % (err.obj, err.rank, err.msg) + use_name = err.obj.name if err.obj.id != err.obj.name else None + prop_formatted = "Property[id=%s|%s]" % (err.obj.id, use_name) + msg = "%s\n Validation[%s]: %s" % (prop_formatted, err.rank, err.msg) print(msg) def __len__(self): diff --git a/odml/section.py b/odml/section.py index e37a7d8a..5c4f61ac 100644 --- a/odml/section.py +++ b/odml/section.py @@ -26,14 +26,17 @@ class BaseSection(base.Sectionable): An odML Section. :param name: string providing the name of the Section. If the name is not - provided, the uuid of the Property is assigned as its name. + provided, the object id of the Section is assigned as its name. + Section name is a required attribute. :param type: String providing a grouping description for similar Sections. + Section type is a required attribute and will be set to the string + 'n.s.' by default. :param parent: the parent object of the new Section. If the object is not an odml.Section or an odml.Document, a ValueError is raised. - :param definition: String describing the definition of the Section. + :param definition: String defining this Section. :param reference: A reference (e.g. an URL) to an external definition of the Section. - :param repository: URL to a repository where this Section can be found. + :param repository: URL to a repository in which the Section is defined. :param link: Specifies a soft link, i.e. a path within the document. :param include: Specifies an arbitrary URL. Can only be used if *link* is not set. :param oid: object id, UUID string as specified in RFC 4122. If no id is provided, @@ -49,7 +52,7 @@ class BaseSection(base.Sectionable): _format = fmt.Section - def __init__(self, name=None, type=None, parent=None, + def __init__(self, name=None, type="n.s.", parent=None, definition=None, reference=None, repository=None, link=None, include=None, oid=None): @@ -84,7 +87,9 @@ def __init__(self, name=None, type=None, parent=None, for err in validation.Validation(self).errors: if err.is_error: - msg = "\n\t- %s %s: %s" % (err.obj, err.rank, err.msg) + use_name = err.obj.name if err.obj.id != err.obj.name else None + sec_formatted = "Section[id=%s|%s/%s]" % (err.obj.id, use_name, err.obj.type) + msg = "%s\n Validation[%s]: %s" % (sec_formatted, err.rank, err.msg) print(msg) def __repr__(self): diff --git a/odml/validation.py b/odml/validation.py index 7f8b9386..c4c2652b 100644 --- a/odml/validation.py +++ b/odml/validation.py @@ -133,13 +133,12 @@ def object_required_attributes(obj): args = obj.format().arguments for arg in args: if arg[1] == 1: + msg = "Missing required attribute '%s'" % (arg[0]) if not hasattr(obj, arg[0]): - msg = "Missing attribute %s for %s" % (arg[0], obj.format().name.capitalize()) yield ValidationError(obj, msg, LABEL_ERROR) continue obj_arg = getattr(obj, arg[0]) if not obj_arg and not isinstance(obj_arg, bool): - msg = "%s %s undefined" % (obj.format().name.capitalize(), arg[0]) yield ValidationError(obj, msg, LABEL_ERROR) @@ -150,12 +149,12 @@ def object_required_attributes(obj): def section_type_must_be_defined(sec): """ - Tests that no Section has an undefined type. + Tests that no Section has an unspecified type and adds a warning otherwise. :param sec: odml.Section. """ - if sec.type is None or sec.type == '' or sec.type == 'undefined': - yield ValidationError(sec, 'Section type undefined', LABEL_WARNING) + if sec.type and sec.type == "n.s.": + yield ValidationError(sec, "Section type not specified", LABEL_WARNING) Validation.register_handler('section', section_type_must_be_defined) diff --git a/test/test_doc.py b/test/test_doc.py index f28b2f5c..69969e4a 100644 --- a/test/test_doc.py +++ b/test/test_doc.py @@ -312,7 +312,7 @@ def test_create_section(self): self.assertEqual(len(root.sections), 2) self.assertEqual(subsec.parent, root) self.assertEqual(root.sections[name], subsec) - self.assertEqual(root.sections[name].type, "undefined") + self.assertEqual(root.sections[name].type, "n.s.") name = "subsubsec" subsec = root.sections[0].create_section(name) diff --git a/test/test_section.py b/test/test_section.py index ae990199..a687df6b 100644 --- a/test/test_section.py +++ b/test/test_section.py @@ -906,7 +906,7 @@ def test_create_section(self): self.assertEqual(len(root.sections), 2) self.assertEqual(subsec.parent, root) self.assertEqual(root.sections[name], subsec) - self.assertEqual(root.sections[name].type, "undefined") + self.assertEqual(root.sections[name].type, "n.s.") name = "subsubsec" subsec = root.sections[0].create_section(name) diff --git a/test/test_validation.py b/test/test_validation.py index e31cccff..47c42f9b 100644 --- a/test/test_validation.py +++ b/test/test_validation.py @@ -84,12 +84,6 @@ def test_property_values_cardinality(self): self.assertFalse(err.is_error) self.assertIn(test_msg, err.msg) - def test_section_type(self): - doc = samplefile.parse("""s1[undefined]""") - res = validate(doc) - # the section type is undefined (also in the mapping) - self.assertError(res, "Section type undefined") - def test_section_in_terminology(self): doc = samplefile.parse("""s1[T1]""") res = validate(doc) @@ -202,17 +196,12 @@ def test_property_name_readable(self): def test_standalone_section(self): """ Test if standalone section does not return errors if required attributes are correct. - If type is undefined, check error message. + If type is not specified, check error message. """ sec_one = odml.Section("sec1") - res = validate(sec_one) - self.assertError(res, "Section type undefined") - - doc = samplefile.parse("""s1[undefined]""") - res = validate(doc) - self.assertError(res, "Section type undefined") + self.assertError(res, "Section type not specified") def test_standalone_property(self): """ @@ -222,20 +211,22 @@ def test_standalone_property(self): prop = odml.Property() prop.type = "" - assert len(list(filter(lambda x: x.is_error, validate(prop).errors))) == 0 + errs = list(filter(lambda x: x.is_error, validate(prop).errors)) + self.assertEquals(len(errs), 0) def test_section_init(self): """ Test validation errors printed to stdout on section init. """ - val_errs = StringIO() + check_msg = "Missing required attribute 'type'" + val_errs = StringIO() old_stdout = sys.stdout sys.stdout = val_errs odml.Section(name="sec", type=None) sys.stdout = old_stdout - assert "Section type undefined" in val_errs.getvalue() + self.assertIn(check_msg, val_errs.getvalue()) def test_prop_string_values(self): """ @@ -245,127 +236,69 @@ def test_prop_string_values(self): prop0 = odml.Property(name='words', dtype="string", values=['-13', '101', '-11', 'hello']) - assert len(validate(prop0).errors) == 0 + self.assertEquals(len(validate(prop0).errors), 0) + + msg_base = 'Dtype of property "%s" currently is "string", but might fit dtype "%s"!' prop1 = odml.Property(name='members', dtype="string", values=['-13', '101', '-11', '0', '-8']) - self.assertError(validate(prop1), 'Dtype of property "members" currently is "string",' - ' but might fit dtype "int"!') + self.assertError(validate(prop1), msg_base % ("members", "int")) prop2 = odml.Property(name='potential', dtype="string", values=['-4.8', '10.0', '-11.9', '-10.0', '18.0']) - self.assertError(validate(prop2), 'Dtype of property "potential" currently is "string", ' - 'but might fit dtype "float"!') + self.assertError(validate(prop2), msg_base % ("potential", "float")) prop3 = odml.Property(name='dates', dtype="string", values=['1997-12-14', '00-12-14', '89-07-04']) - self.assertError(validate(prop3), 'Dtype of property "dates" currently is "string", ' - 'but might fit dtype "date"!') + self.assertError(validate(prop3), msg_base % ("dates", "date")) prop4 = odml.Property(name='datetimes', dtype="string", values=['97-12-14 11:11:11', '97-12-14 12:12', '1997-12-14 03:03']) - self.assertError(validate(prop4), 'Dtype of property "datetimes" currently is "string", ' - 'but might fit dtype "datetime"!') + self.assertError(validate(prop4), msg_base % ("datetimes", "datetime")) prop5 = odml.Property(name='times', dtype="string", values=['11:11:11', '12:12:12', '03:03:03']) - self.assertError(validate(prop5), 'Dtype of property "times" currently is "string", ' - 'but might fit dtype "time"!') + self.assertError(validate(prop5), msg_base % ("times", "time")) prop6 = odml.Property(name='sent', dtype="string", values=['False', True, 'TRUE', False, 't']) - self.assertError(validate(prop6), 'Dtype of property "sent" currently is "string", ' - 'but might fit dtype "boolean"!') + self.assertError(validate(prop6), msg_base % ("sent", "boolean")) prop7 = odml.Property(name='texts', dtype="string", values=['line1\n line2', 'line3\n line4', '\nline5\nline6']) - self.assertError(validate(prop7), 'Dtype of property "texts" currently is "string", ' - 'but might fit dtype "text"!') + self.assertError(validate(prop7), msg_base % ("texts", "text")) prop8 = odml.Property(name="Location", dtype='string', values=['(39.12; 67.19)', '(39.12; 67.19)', '(39.12; 67.18)']) - self.assertError(validate(prop8), 'Dtype of property "Location" currently is "string", ' - 'but might fit dtype "2-tuple"!') + self.assertError(validate(prop8), msg_base % ("Location", "2-tuple")) prop9 = odml.Property(name="Coos", dtype='string', values=['(39.12; 89; 67.19)', '(39.12; 78; 67.19)', '(39.12; 56; 67.18)']) - self.assertError(validate(prop9), 'Dtype of property "Coos" currently is "string", ' - 'but might fit dtype "3-tuple"!') + self.assertError(validate(prop9), msg_base % ("Coos", "3-tuple")) - def test_load_section_xml(self): - """ - Test if loading xml document raises validation errors for Sections with undefined type. - """ + def load_section_validation(self, doc): + filter_func = lambda x: x.msg == filter_msg and x.obj.name == filter_name - path = os.path.join(self.dir_path, "resources", "validation_section.xml") - doc = odml.load(path) + # Check error for deliberate empty section type + filter_msg = "Missing required attribute 'type'" + filter_name = "sec_type_empty" + self.assertGreater(len(list(filter(filter_func, validate(doc).errors))), 0) - assert len(list(filter( - lambda x: x.msg == "Section type undefined" and x.obj.name == "sec_type_undefined", - validate(doc).errors))) > 0 - assert len(list(filter( - lambda x: x.msg == "Section type undefined" and x.obj.name == "sec_type_empty", - validate(doc).errors))) > 0 + # Check warning for not specified section type + filter_msg = "Section type not specified" + filter_name = "sec_type_undefined" + self.assertGreater(len(list(filter(filter_func, validate(doc).errors))), 0) - def test_load_dtypes_xml(self): + def test_load_section_xml(self): """ - Test if loading xml document raises validation errors for Properties with undefined dtypes. + Test if loading xml document raises validation errors for Sections with undefined type. """ - path = os.path.join(self.dir_path, "resources", "validation_dtypes.xml") + path = os.path.join(self.dir_path, "resources", "validation_section.xml") doc = odml.load(path) - self.assertError(validate(doc), 'Dtype of property "members_no" currently is "string", ' - 'but might fit dtype "int"!') - - self.assertError(validate(doc), 'Dtype of property "potential_no" currently is "string", ' - 'but might fit dtype "float"!') - - self.assertError(validate(doc), 'Dtype of property "dates_no" currently is "string", ' - 'but might fit dtype "date"!') - - self.assertError(validate(doc), 'Dtype of property "datetimes_no" currently is "string", ' - 'but might fit dtype "datetime"!') - - self.assertError(validate(doc), 'Dtype of property "times_no" currently is "string", ' - 'but might fit dtype "time"!') - - self.assertError(validate(doc), 'Dtype of property "sent_no" currently is "string", ' - 'but might fit dtype "boolean"!') - - self.assertError(validate(doc), 'Dtype of property "Location_no" currently is "string", ' - 'but might fit dtype "2-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "Coos_no" currently is "string", ' - 'but might fit dtype "3-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "members_mislabelled" currently is ' - '"string", but might fit dtype "int"!') - - self.assertError(validate(doc), 'Dtype of property "potential_mislabelled" currently is ' - '"string", but might fit dtype "float"!') - - self.assertError(validate(doc), 'Dtype of property "dates_mislabelled" currently is ' - '"string", but might fit dtype "date"!') - - self.assertError(validate(doc), 'Dtype of property "datetimes_mislabelled" currently is ' - '"string", but might fit dtype "datetime"!') - - self.assertError(validate(doc), 'Dtype of property "times_mislabelled" currently is ' - '"string", but might fit dtype "time"!') - - self.assertError(validate(doc), 'Dtype of property "sent_mislabelled" currently is ' - '"string", but might fit dtype "boolean"!') - - self.assertError(validate(doc), 'Dtype of property "texts_mislabelled" currently is ' - '"string", but might fit dtype "text"!') - - self.assertError(validate(doc), 'Dtype of property "Location_mislabelled" currently is ' - '"string", but might fit dtype "2-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "Coos_mislabelled" currently is ' - '"string", but might fit dtype "3-tuple"!') + self.load_section_validation(doc) def test_load_section_json(self): """ @@ -375,142 +308,67 @@ def test_load_section_json(self): path = os.path.join(self.dir_path, "resources", "validation_section.json") doc = odml.load(path, "JSON") - assert len(list(filter( - lambda x: x.msg == "Section type undefined" and x.obj.name == "sec_type_undefined", - validate(doc).errors))) > 0 - assert len(list(filter( - lambda x: x.msg == "Section type undefined" and x.obj.name == "sec_type_empty", - validate(doc).errors))) > 0 + self.load_section_validation(doc) - def test_load_dtypes_json(self): + def test_load_section_yaml(self): """ - Test if loading json document raises validation errors for Properties with undefined dtypes. + Test if loading yaml document raises validation errors for Sections with undefined type. """ - path = os.path.join(self.dir_path, "resources", "validation_dtypes.json") - doc = odml.load(path, "JSON") - - self.assertError(validate(doc), 'Dtype of property "members_no" currently is "string", ' - 'but might fit dtype "int"!') - - self.assertError(validate(doc), 'Dtype of property "potential_no" currently is "string", ' - 'but might fit dtype "float"!') - - self.assertError(validate(doc), 'Dtype of property "dates_no" currently is "string", ' - 'but might fit dtype "date"!') - - self.assertError(validate(doc), 'Dtype of property "datetimes_no" currently is "string", ' - 'but might fit dtype "datetime"!') - - self.assertError(validate(doc), 'Dtype of property "times_no" currently is "string", ' - 'but might fit dtype "time"!') - - self.assertError(validate(doc), 'Dtype of property "sent_no" currently is "string", ' - 'but might fit dtype "boolean"!') - - self.assertError(validate(doc), 'Dtype of property "Location_no" currently is "string", ' - 'but might fit dtype "2-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "Coos_no" currently is "string", ' - 'but might fit dtype "3-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "members_mislabelled" currently is ' - '"string", but might fit dtype "int"!') - - self.assertError(validate(doc), 'Dtype of property "potential_mislabelled" currently is ' - '"string", but might fit dtype "float"!') - - self.assertError(validate(doc), 'Dtype of property "dates_mislabelled" currently is ' - '"string", but might fit dtype "date"!') - - self.assertError(validate(doc), 'Dtype of property "datetimes_mislabelled" currently is ' - '"string", but might fit dtype "datetime"!') - - self.assertError(validate(doc), 'Dtype of property "times_mislabelled" currently is ' - '"string", but might fit dtype "time"!') - - self.assertError(validate(doc), 'Dtype of property "sent_mislabelled" currently is ' - '"string", but might fit dtype "boolean"!') + path = os.path.join(self.dir_path, "resources", "validation_section.yaml") + doc = odml.load(path, "YAML") - self.assertError(validate(doc), 'Dtype of property "texts_mislabelled" currently is ' - '"string", but might fit dtype "text"!') + self.load_section_validation(doc) + + def load_dtypes_validation(self, doc): + msg_base = 'Dtype of property "%s" currently is "string", but might fit dtype "%s"!' + + doc_val = validate(doc) + self.assertError(doc_val, msg_base % ("members_no", "int")) + self.assertError(doc_val, msg_base % ("potential_no", "float")) + self.assertError(doc_val, msg_base % ("dates_no", "date")) + self.assertError(doc_val, msg_base % ("datetimes_no", "datetime")) + self.assertError(doc_val, msg_base % ("times_no", "time")) + self.assertError(doc_val, msg_base % ("sent_no", "boolean")) + self.assertError(doc_val, msg_base % ("Location_no", "2-tuple")) + self.assertError(doc_val, msg_base % ("Coos_no", "3-tuple")) + + self.assertError(doc_val, msg_base % ("members_mislabelled", "int")) + self.assertError(doc_val, msg_base % ("potential_mislabelled", "float")) + self.assertError(doc_val, msg_base % ("dates_mislabelled", "date")) + self.assertError(doc_val, msg_base % ("datetimes_mislabelled", "datetime")) + self.assertError(doc_val, msg_base % ("times_mislabelled", "time")) + self.assertError(doc_val, msg_base % ("sent_mislabelled", "boolean")) + self.assertError(doc_val, msg_base % ("texts_mislabelled", "text")) + self.assertError(doc_val, msg_base % ("Location_mislabelled", "2-tuple")) + self.assertError(doc_val, msg_base % ("Coos_mislabelled", "3-tuple")) - self.assertError(validate(doc), 'Dtype of property "Location_mislabelled" currently is ' - '"string", but might fit dtype "2-tuple"!') + def test_load_dtypes_xml(self): + """ + Test if loading xml document raises validation errors + for Properties with undefined dtypes. + """ - self.assertError(validate(doc), 'Dtype of property "Coos_mislabelled" currently is ' - '"string", but might fit dtype "3-tuple"!') + path = os.path.join(self.dir_path, "resources", "validation_dtypes.xml") + doc = odml.load(path) + self.load_dtypes_validation(doc) - def test_load_section_yaml(self): + def test_load_dtypes_json(self): """ - Test if loading yaml document raises validation errors for Sections with undefined type. + Test if loading json document raises validation errors + for Properties with undefined dtypes. """ - path = os.path.join(self.dir_path, "resources", "validation_section.yaml") - doc = odml.load(path, "YAML") - - assert len(list(filter( - lambda x: x.msg == "Section type undefined" and x.obj.name == "sec_type_undefined", - validate(doc).errors))) > 0 - assert len(list(filter( - lambda x: x.msg == "Section type undefined" and x.obj.name == "sec_type_empty", - validate(doc).errors))) > 0 + path = os.path.join(self.dir_path, "resources", "validation_dtypes.json") + doc = odml.load(path, "JSON") + self.load_dtypes_validation(doc) def test_load_dtypes_yaml(self): """ - Test if loading yaml document raises validation errors for Properties with undefined dtypes. + Test if loading yaml document raises validation errors + for Properties with undefined dtypes. """ path = os.path.join(self.dir_path, "resources", "validation_dtypes.yaml") doc = odml.load(path, "YAML") - - self.assertError(validate(doc), 'Dtype of property "members_no" currently is "string", ' - 'but might fit dtype "int"!') - - self.assertError(validate(doc), 'Dtype of property "potential_no" currently is "string", ' - 'but might fit dtype "float"!') - - self.assertError(validate(doc), 'Dtype of property "dates_no" currently is "string", ' - 'but might fit dtype "date"!') - - self.assertError(validate(doc), 'Dtype of property "datetimes_no" currently is "string", ' - 'but might fit dtype "datetime"!') - - self.assertError(validate(doc), 'Dtype of property "times_no" currently is "string", ' - 'but might fit dtype "time"!') - - self.assertError(validate(doc), 'Dtype of property "sent_no" currently is "string", ' - 'but might fit dtype "boolean"!') - - self.assertError(validate(doc), 'Dtype of property "Location_no" currently is "string", ' - 'but might fit dtype "2-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "Coos_no" currently is "string", ' - 'but might fit dtype "3-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "members_mislabelled" currently is ' - '"string", but might fit dtype "int"!') - - self.assertError(validate(doc), 'Dtype of property "potential_mislabelled" currently is ' - '"string", but might fit dtype "float"!') - - self.assertError(validate(doc), 'Dtype of property "dates_mislabelled" currently is ' - '"string", but might fit dtype "date"!') - - self.assertError(validate(doc), 'Dtype of property "datetimes_mislabelled" currently is ' - '"string", but might fit dtype "datetime"!') - - self.assertError(validate(doc), 'Dtype of property "times_mislabelled" currently is ' - '"string", but might fit dtype "time"!') - - self.assertError(validate(doc), 'Dtype of property "sent_mislabelled" currently is ' - '"string", but might fit dtype "boolean"!') - - self.assertError(validate(doc), 'Dtype of property "texts_mislabelled" currently is ' - '"string", but might fit dtype "text"!') - - self.assertError(validate(doc), 'Dtype of property "Location_mislabelled" currently is ' - '"string", but might fit dtype "2-tuple"!') - - self.assertError(validate(doc), 'Dtype of property "Coos_mislabelled" currently is ' - '"string", but might fit dtype "3-tuple"!') + self.load_dtypes_validation(doc)