Skip to content

Commit f6fefe0

Browse files
committed
[validation] Add general cardinality function
1 parent 416bc27 commit f6fefe0

File tree

1 file changed

+89
-57
lines changed

1 file changed

+89
-57
lines changed

odml/validation.py

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ def __repr__(self):
5353
class Validation(object):
5454
"""
5555
Validation provides a set of default validations that can used to validate
56-
an odml.Document. Custom validations can be added via the 'register_handler' method.
56+
odml objects. Custom validations can be added via the 'register_handler' method.
5757
58-
:param doc: odml.Document that the validation will be applied to.
58+
:param obj: odml object the validation will be applied to.
5959
"""
6060

6161
_handlers = {}
@@ -77,19 +77,18 @@ def register_handler(klass, handler):
7777
"""
7878
Validation._handlers.setdefault(klass, set()).add(handler)
7979

80-
def __init__(self, obj):
81-
self.doc = obj # may also be a section
80+
def __init__(self, obj, validate=True, reset=False):
81+
self.obj = obj # may also be a section
8282
self.errors = []
8383

84-
self.validate(obj)
85-
86-
if obj.format().name == "property":
84+
# If initialized with reset=True, reset all handlers and
85+
# do not run any validation yet to allow custom Validation objects.
86+
if reset:
87+
self._handlers = {}
8788
return
8889

89-
for sec in obj.itersections(recursive=True):
90-
self.validate(sec)
91-
for prop in sec.properties:
92-
self.validate(prop)
90+
if validate:
91+
self.run_validation()
9392

9493
def validate(self, obj):
9594
"""
@@ -109,6 +108,38 @@ def error(self, validation_error):
109108
"""
110109
self.errors.append(validation_error)
111110

111+
def run_validation(self):
112+
"""
113+
Runs a clean new validation on the registered Validation object.
114+
"""
115+
self.errors = []
116+
117+
self.validate(self.obj)
118+
119+
if self.obj.format().name == "property":
120+
return
121+
122+
for sec in self.obj.itersections(recursive=True):
123+
self.validate(sec)
124+
for prop in sec.properties:
125+
self.validate(prop)
126+
127+
def register_custom_handler(self, klass, handler):
128+
"""
129+
Adds a validation handler for an odml class. The handler is called in the
130+
validation process for each corresponding object.
131+
The *handler* is assumed to be a generator function yielding
132+
all ValidationErrors it finds.
133+
134+
Section handlers are only called for sections and not for the document node.
135+
If both are required, the handler needs to be registered twice.
136+
137+
:param klass: string corresponding to an odml class. Valid strings are
138+
'odML', 'section' and 'property'.
139+
:param handler: validation function applied to the odml class.
140+
"""
141+
self._handlers.setdefault(klass, set()).add(handler)
142+
112143
def __getitem__(self, obj):
113144
"""
114145
Return a list of the errors for a certain object.
@@ -455,88 +486,89 @@ def property_values_string_check(prop):
455486
Validation.register_handler('property', property_values_string_check)
456487

457488

458-
def section_properties_cardinality(obj):
489+
def _cardinality_validation(obj, cardinality, card_target_attr, validation_rank):
459490
"""
460-
Checks Section properties against any set property cardinality.
491+
Helper function that validates the cardinality of an odml object attribute.
492+
Valid object-attribute combinations are Section-sections, Section-properties and
493+
Property-values.
461494
462-
:param obj: odml.Section
463-
:return: Yields a ValidationError warning, if a set cardinality is not met.
495+
:param obj: an odml.Section or an odml.Property
496+
:param cardinality: 2-int tuple containing the cardinality value
497+
:param card_target_attr: string containing the name of the attribute the cardinality is
498+
applied against. Supported values are:
499+
'sections', 'properties' or 'values'
500+
:param validation_rank: Rank of the yielded ValidationError.
501+
502+
:return: Returns a ValidationError, if a set cardinality is not met or None.
464503
"""
465-
if obj.prop_cardinality and isinstance(obj.prop_cardinality, tuple):
504+
err = None
505+
if cardinality and isinstance(cardinality, tuple):
466506

467-
val_min = obj.prop_cardinality[0]
468-
val_max = obj.prop_cardinality[1]
507+
val_min = cardinality[0]
508+
val_max = cardinality[1]
469509

470-
val_len = len(obj.properties) if obj.properties else 0
510+
card_target = getattr(obj, card_target_attr)
511+
val_len = len(card_target) if card_target else 0
471512

472513
invalid_cause = ""
473514
if val_min and val_len < val_min:
474515
invalid_cause = "minimum %s" % val_min
475-
elif val_max and (obj.properties and len(obj.properties) > val_max):
516+
elif val_max and val_len > val_max:
476517
invalid_cause = "maximum %s" % val_max
477518

478519
if invalid_cause:
479-
msg = "Section properties cardinality violated"
520+
obj_name = obj.format().name.capitalize()
521+
msg = "%s %s cardinality violated" % (obj_name, card_target_attr)
480522
msg += " (%s values, %s found)" % (invalid_cause, val_len)
481-
yield ValidationError(obj, msg, LABEL_WARNING)
482523

524+
err = ValidationError(obj, msg, validation_rank)
483525

484-
Validation.register_handler("section", section_properties_cardinality)
526+
return err
485527

486528

487-
def section_sections_cardinality(obj):
529+
def section_properties_cardinality(obj):
488530
"""
489-
Checks Section sub-sections against any set sub-section cardinality.
531+
Checks Section properties against any set property cardinality.
490532
491533
:param obj: odml.Section
534+
492535
:return: Yields a ValidationError warning, if a set cardinality is not met.
493536
"""
494-
if obj.sec_cardinality and isinstance(obj.sec_cardinality, tuple):
537+
err = _cardinality_validation(obj, obj.prop_cardinality, 'properties', LABEL_WARNING)
538+
if err:
539+
yield err
495540

496-
val_min = obj.sec_cardinality[0]
497-
val_max = obj.sec_cardinality[1]
498541

499-
val_len = len(obj.sections) if obj.sections else 0
542+
Validation.register_handler("section", section_properties_cardinality)
500543

501-
invalid_cause = ""
502-
if val_min and val_len < val_min:
503-
invalid_cause = "minimum %s" % val_min
504-
elif val_max and (obj.sections and len(obj.sections) > val_max):
505-
invalid_cause = "maximum %s" % val_max
506544

507-
if invalid_cause:
508-
msg = "Section sub-section cardinality violated"
509-
msg += " (%s values, %s found)" % (invalid_cause, val_len)
510-
yield ValidationError(obj, msg, LABEL_WARNING)
545+
def section_sections_cardinality(obj):
546+
"""
547+
Checks Section sub-sections against any set sub-section cardinality.
548+
549+
:param obj: odml.Section
550+
551+
:return: Yields a ValidationError warning, if a set cardinality is not met.
552+
"""
553+
err = _cardinality_validation(obj, obj.sec_cardinality, 'sections', LABEL_WARNING)
554+
if err:
555+
yield err
511556

512557

513558
Validation.register_handler("section", section_sections_cardinality)
514559

515560

516-
def property_values_cardinality(prop):
561+
def property_values_cardinality(obj):
517562
"""
518563
Checks Property values against any set value cardinality.
519564
520-
:param prop: odml.Property
565+
:param obj: odml.Property
566+
521567
:return: Yields a ValidationError warning, if a set cardinality is not met.
522568
"""
523-
if prop.val_cardinality and isinstance(prop.val_cardinality, tuple):
524-
525-
val_min = prop.val_cardinality[0]
526-
val_max = prop.val_cardinality[1]
527-
528-
val_len = len(prop.values) if prop.values else 0
529-
530-
invalid_cause = ""
531-
if val_min and val_len < val_min:
532-
invalid_cause = "minimum %s" % val_min
533-
elif val_max and (prop.values and len(prop.values) > val_max):
534-
invalid_cause = "maximum %s" % val_max
535-
536-
if invalid_cause:
537-
msg = "Property values cardinality violated"
538-
msg += " (%s values, %s found)" % (invalid_cause, val_len)
539-
yield ValidationError(prop, msg, LABEL_WARNING)
569+
err = _cardinality_validation(obj, obj.val_cardinality, 'values', LABEL_WARNING)
570+
if err:
571+
yield err
540572

541573

542574
Validation.register_handler("property", property_values_cardinality)

0 commit comments

Comments
 (0)