Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6fac3ca
[validation.py] Add Property Values Validation Prototype
fschrader1992 Mar 6, 2020
865a6be
[validation.py] Add Validation from odMl Attributes List
fschrader1992 Mar 6, 2020
e45181f
[validation.py] Remove Section Type Definition Validation Handler
fschrader1992 Mar 6, 2020
e8d1a99
[validation.py] Allow Standalone Property Validation
fschrader1992 Mar 6, 2020
95a7f7c
[section.py] Add Validation on Initialization
fschrader1992 Mar 6, 2020
b2d62aa
[property.py] Add Validation on Initialization
fschrader1992 Mar 6, 2020
65ab503
[test_validation.py] Add Test for Standalone Section
fschrader1992 Mar 6, 2020
af8860b
[test_validation.py] Add Test for Standalone Property
fschrader1992 Mar 6, 2020
a68a1f2
[validation.py] Move Check For Standalone Property
fschrader1992 Mar 10, 2020
74684c0
[validation.py] Reintroduce Section Type Check
fschrader1992 Mar 10, 2020
6d4a94a
[validation.py] Adapt Required Attributes Check
fschrader1992 Mar 10, 2020
8d05bd4
[validation.py] Minor Changes for Property Values Check Prototype
fschrader1992 Mar 10, 2020
18e2c19
[test_validation.py] Specify Test for Standalone Section
fschrader1992 Mar 10, 2020
1c074b9
[test_validation.py] Specify Test for Standalone Property
fschrader1992 Mar 10, 2020
01504d3
[section.py] Print Validation Errors
fschrader1992 Mar 10, 2020
c028491
[property.py] Print Validation Errors
fschrader1992 Mar 10, 2020
a22d11e
Merge branch 'master' of https://github.com/G-Node/python-odml
fschrader1992 Mar 17, 2020
76a1c4f
[property.py] Change Validation Error Messages Print
fschrader1992 Mar 17, 2020
b3409c0
[section.py] Change Validation Error Messages Print
fschrader1992 Mar 17, 2020
20f02f4
[validation.py] Adapt Object Has Attribute Check
fschrader1992 Mar 17, 2020
7946515
[validation.py] Replace Empty String Statement
fschrader1992 Mar 17, 2020
5778db1
[validation.py] Set Property DType Error Label to Warning
fschrader1992 Mar 17, 2020
ca77e62
[validation.py] Add Check for Property Value Tuple Length
fschrader1992 Mar 17, 2020
6ff4cfb
[validation.py] Register Handler for Property Value Check
fschrader1992 Mar 17, 2020
6466e9d
[test_validation.py] Remove Property from Standalone Section Test
fschrader1992 Mar 17, 2020
bb34306
[test_validation.py] Add Section Type Undefined Test to Standalone Se…
fschrader1992 Mar 17, 2020
a04147a
[test_validation.py] Change to Empty Property Type for Standalone Pro…
fschrader1992 Mar 17, 2020
f7a2b2f
[test_validation.py] Change Comments for Standalone Tests
fschrader1992 Mar 17, 2020
426f1f4
[validation.py] Remove Prototype Comment
fschrader1992 Mar 18, 2020
009b3a2
[validation.py] Change Label and Return for Attribute Check
fschrader1992 Mar 18, 2020
a920a49
[validation.py] Change Error Label
fschrader1992 Mar 18, 2020
329588a
[test] Adapt Tests to Section Type Requirement
fschrader1992 Mar 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions odml/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from . import base
from . import dtypes
from . import validation
from . import format as frmt
from .tools.doc_inherit import inherit_docstring, allow_inherit_docstring

Expand Down Expand Up @@ -128,6 +129,11 @@ def __init__(self, name=None, values=None, parent=None, unit=None,

self.parent = parent

for err in validation.Validation(self).errors:
if err.is_error:
msg = "\n\t- %s %s: %s" % (err.obj, err.rank, err.msg)
print(msg)

def __len__(self):
return len(self._values)

Expand Down
6 changes: 6 additions & 0 deletions odml/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from . import base
from . import format as fmt
from . import terminology
from . import validation
from .doc import BaseDocument
# this is supposedly ok, as we only use it for an isinstance check
from .property import BaseProperty
Expand Down Expand Up @@ -81,6 +82,11 @@ def __init__(self, name=None, type=None, parent=None,
self.type = type
self.parent = parent

for err in validation.Validation(self).errors:
if err.is_error:
msg = "\n\t- %s %s: %s" % (err.obj, err.rank, err.msg)
print(msg)

def __repr__(self):
return "Section[%d|%d] {name = %s, type = %s, id = %s}" % (len(self._sections),
len(self._props),
Expand Down
77 changes: 73 additions & 4 deletions odml/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Generic odML validation framework.
"""

from . import dtypes

LABEL_ERROR = 'error'
LABEL_WARNING = 'warning'

Expand Down Expand Up @@ -74,12 +76,16 @@ def register_handler(klass, handler):
"""
Validation._handlers.setdefault(klass, set()).add(handler)

def __init__(self, doc):
self.doc = doc # may also be a section
def __init__(self, obj):
self.doc = obj # may also be a section
self.errors = []
self.validate(doc)

for sec in doc.itersections(recursive=True):
self.validate(obj)

if obj.format().name == "property":
return

for sec in obj.itersections(recursive=True):
self.validate(sec)
for prop in sec.properties:
self.validate(prop)
Expand Down Expand Up @@ -116,6 +122,31 @@ def __getitem__(self, obj):
# ------------------------------------------------
# validation rules

def object_required_attributes(obj):
"""
Tests that no Object has undefined attributes, given in format.

:param obj: document, section or property.
"""

args = obj.format().arguments
for arg in args:
if arg[1] == 1:
if not hasattr(obj, arg[0]):
msg = "Missing attribute %s for %s" % (obj.format().name.capitalize(), arg[0])
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)


Validation.register_handler('odML', object_required_attributes)
Validation.register_handler('section', object_required_attributes)
Validation.register_handler('property', object_required_attributes)


def section_type_must_be_defined(sec):
"""
Tests that no Section has an undefined type.
Expand Down Expand Up @@ -282,6 +313,9 @@ def property_terminology_check(prop):
2. warn, if there are multiple values with different units or the unit does
not match the one in the terminology.
"""
if not prop.parent:
return

tsec = prop.parent.get_terminology_equivalent()
if tsec is None:
return
Expand All @@ -300,6 +334,9 @@ def property_dependency_check(prop):
Produces a warning if the dependency attribute refers to a non-existent attribute
or the dependency_value does not match.
"""
if not prop.parent:
return

dep = prop.dependency
if dep is None:
return
Expand All @@ -317,3 +354,35 @@ def property_dependency_check(prop):


Validation.register_handler('property', property_dependency_check)


def property_values_check(prop):
"""
Tests that the values are of consistent dtype.
If dtype is not given, infer from first item in list.

:param prop: property the validation is applied on.
"""

if prop.dtype is not None and prop.dtype != "":
dtype = prop.dtype
elif prop.values:
dtype = dtypes.infer_dtype(prop.values[0])
else:
return

for val in prop.values:
if dtype.endswith("-tuple"):
tuple_len = int(dtype[:-6])
if len(val) != tuple_len:
msg = "Tuple of length %s not consistent with dtype %s!" % (len(val), dtype)
yield ValidationError(prop, msg, LABEL_WARNING)
else:
try:
dtypes.get(val, dtype)
except ValueError:
msg = "Property values not of consistent dtype!"
yield ValidationError(prop, msg, LABEL_WARNING)


Validation.register_handler('property', property_values_check)
19 changes: 10 additions & 9 deletions test/test_doc_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,26 @@ def test_children(self):
"""
This test checks the correct saving and loading of Section children of a Document.
"""
s_type = "type"
# Lvl 1 child Sections
sec_lvl_11 = odml.Section(name="sec_11", parent=self.doc)
_ = odml.Section(name="sec_12", parent=self.doc)
sec_lvl_11 = odml.Section(name="sec_11", type=s_type, parent=self.doc)
_ = odml.Section(name="sec_12", type=s_type, parent=self.doc)

# Lvl 2 child Sections
sec_lvl_21 = odml.Section(name="sec_21", parent=sec_lvl_11)
_ = odml.Section(name="sec_22", parent=sec_lvl_11)
_ = odml.Section(name="sec_23", parent=sec_lvl_11)
sec_lvl_21 = odml.Section(name="sec_21", type=s_type, parent=sec_lvl_11)
_ = odml.Section(name="sec_22", type=s_type, parent=sec_lvl_11)
_ = odml.Section(name="sec_23", type=s_type, parent=sec_lvl_11)

# Lvl 2 child Properties
_ = odml.Property(name="prop_21", parent=sec_lvl_11)
_ = odml.Property(name="prop_22", parent=sec_lvl_11)
_ = odml.Property(name="prop_23", parent=sec_lvl_11)

# Lvl 3 child Sections
_ = odml.Section(name="sec_31", parent=sec_lvl_21)
_ = odml.Section(name="sec_32", parent=sec_lvl_21)
_ = odml.Section(name="sec_33", parent=sec_lvl_21)
_ = odml.Section(name="sec_34", parent=sec_lvl_21)
_ = odml.Section(name="sec_31", type=s_type, parent=sec_lvl_21)
_ = odml.Section(name="sec_32", type=s_type, parent=sec_lvl_21)
_ = odml.Section(name="sec_33", type=s_type, parent=sec_lvl_21)
_ = odml.Section(name="sec_34", type=s_type, parent=sec_lvl_21)

# Lvl 3 child Properties
_ = odml.Property(name="prop_31", parent=sec_lvl_21)
Expand Down
14 changes: 8 additions & 6 deletions test/test_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ def setUp(self):
self.captured_stdout = StringIO()
sys.stdout = self.captured_stdout

s_type = "type"

self.doc = odml.Document(author='Rave', version='1.0')
s1 = odml.Section(name='Cell')
s1 = odml.Section(name='Cell', type=s_type)
p1 = odml.Property(name='Type', values='Rechargeable')
s1.append(p1)

s2 = odml.Section(name='Electrolyte')
s2 = odml.Section(name='Electrolyte', type=s_type)
p2 = odml.Property(name='Composition', values='Ni-Cd')
s2.append(p2)
s1.append(s2)

s3 = odml.Section(name='Electrode')
s3 = odml.Section(name='Electrode', type=s_type)
p3 = odml.Property(name='Material', values='Nickel')
p4 = odml.Property(name='Models', values=['AA', 'AAA'])
s3.append(p3)
Expand All @@ -41,11 +43,11 @@ def test_dump_doc(self):
odml.tools.dumper.dump_doc(self.doc)
output = [x.strip() for x in self.captured_stdout.getvalue().split('\n') if x]
expected_output = []
expected_output.append("*Cell ()")
expected_output.append("*Cell (type='type')")
expected_output.append(":Type (values=Rechargeable, dtype='string')")
expected_output.append("*Electrolyte ()")
expected_output.append("*Electrolyte (type='type')")
expected_output.append(":Composition (values=Ni-Cd, dtype='string')")
expected_output.append("*Electrode ()")
expected_output.append("*Electrode (type='type')")
expected_output.append(":Material (values=Nickel, dtype='string')")
expected_output.append(":Models (values=[AA,AAA], dtype='string')")
self.assertEqual(len(output), len(expected_output))
Expand Down
26 changes: 14 additions & 12 deletions test/test_section_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ def save_load(self):
def test_id(self):
# Test correct save and load of generated id.
sec_name = "empty_id"
sec = odml.Section(name=sec_name, parent=self.doc)
sec_type = "type"
sec = odml.Section(name=sec_name, type=sec_type, parent=self.doc)

jdoc, xdoc, ydoc = self.save_load()

Expand All @@ -62,7 +63,7 @@ def test_id(self):
# Test correct save and load of assigned id.
sec_name = "assigned_id"
assigned_id = "79b613eb-a256-46bf-84f6-207df465b8f7"
_ = odml.Section(name=sec_name, oid=assigned_id, parent=self.doc)
_ = odml.Section(name=sec_name, oid=assigned_id, type=sec_type, parent=self.doc)

jdoc, xdoc, ydoc = self.save_load()

Expand Down Expand Up @@ -116,31 +117,32 @@ def test_children(self):
This test checks correct writing and loading of Section and Property
children of a Section.
"""
root = odml.Section(name="root", parent=self.doc)
s_type = "type"
root = odml.Section(name="root", type=s_type, parent=self.doc)

# Lvl 1 child Sections
sec_lvl_11 = odml.Section(name="sec_11", parent=root)
_ = odml.Section(name="sec_12", parent=root)
sec_lvl_11 = odml.Section(name="sec_11", type=s_type, parent=root)
_ = odml.Section(name="sec_12", type=s_type, parent=root)

# Lvl 1 child Properties
_ = odml.Property(name="prop_11", parent=root)
_ = odml.Property(name="prop_12", parent=root)

# Lvl 2 child Sections
sec_lvl_21 = odml.Section(name="sec_21", parent=sec_lvl_11)
_ = odml.Section(name="sec_22", parent=sec_lvl_11)
_ = odml.Section(name="sec_23", parent=sec_lvl_11)
sec_lvl_21 = odml.Section(name="sec_21", type=s_type, parent=sec_lvl_11)
_ = odml.Section(name="sec_22", type=s_type, parent=sec_lvl_11)
_ = odml.Section(name="sec_23", type=s_type, parent=sec_lvl_11)

# Lvl 2 child Properties
_ = odml.Property(name="prop_21", parent=sec_lvl_11)
_ = odml.Property(name="prop_22", parent=sec_lvl_11)
_ = odml.Property(name="prop_23", parent=sec_lvl_11)

# Lvl 3 child Sections
_ = odml.Section(name="sec_31", parent=sec_lvl_21)
_ = odml.Section(name="sec_32", parent=sec_lvl_21)
_ = odml.Section(name="sec_33", parent=sec_lvl_21)
_ = odml.Section(name="sec_34", parent=sec_lvl_21)
_ = odml.Section(name="sec_31", type=s_type, parent=sec_lvl_21)
_ = odml.Section(name="sec_32", type=s_type, parent=sec_lvl_21)
_ = odml.Section(name="sec_33", type=s_type, parent=sec_lvl_21)
_ = odml.Section(name="sec_34", type=s_type, parent=sec_lvl_21)

# Lvl 3 child Properties
_ = odml.Property(name="prop_31", parent=sec_lvl_21)
Expand Down
26 changes: 26 additions & 0 deletions test/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,29 @@ def test_section_unique_ids(self):

res = validate(doc)
self.assertError(res, "Duplicate id in Section")

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.
"""

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")

def test_standalone_property(self):
"""
Test if standalone property does not return errors if required attributes are correct.
"""

prop = odml.Property()
prop.type = ""

for err in validate(prop).errors:
assert not err.is_error