From 91d10a1465281a3b2f317019a1cf08ba4689e083 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Wed, 3 Apr 2019 14:41:25 +0200 Subject: [PATCH] Provide basic validation utility in Python --- .../ifcopenshell/validate.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/ifcopenshell-python/ifcopenshell/validate.py diff --git a/src/ifcopenshell-python/ifcopenshell/validate.py b/src/ifcopenshell-python/ifcopenshell/validate.py new file mode 100644 index 0000000000..22e0346eb6 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/validate.py @@ -0,0 +1,103 @@ +from __future__ import print_function + +import ifcopenshell + +named_type = ifcopenshell.ifcopenshell_wrapper.named_type +aggregation_type = ifcopenshell.ifcopenshell_wrapper.aggregation_type +simple_type = ifcopenshell.ifcopenshell_wrapper.simple_type +type_declaration = ifcopenshell.ifcopenshell_wrapper.type_declaration +enumeration_type = ifcopenshell.ifcopenshell_wrapper.enumeration_type +entity_type = ifcopenshell.ifcopenshell_wrapper.entity +select_type = ifcopenshell.ifcopenshell_wrapper.select_type +attribute = ifcopenshell.ifcopenshell_wrapper.attribute + +class ValidationError(Exception): pass + +simple_type_python_mapping = { + "string": str, + "integer": int, + "real": float, + "number": float, + "boolean": bool, + "logical": bool, # still not implemented in IfcOpenShell + "binary": str # maps to a str of "0" and "1" +} + +def assert_valid_inverse(attr, val): + b1, b2 = attr.bound1(), attr.bound2() + invalid = len(val) < b1 or (b2 != -1 and len(val) > b2) + if invalid: + raise ValidationError("%r not valid for %s" % (val, attr)) + return True + +def assert_valid(attr, val): + if isinstance(attr, attribute): + attr_type = attr.type_of_attribute() + else: + attr_type = attr + + type_wrappers = (named_type,) + if not isinstance(val, ifcopenshell.entity_instance): + # If val is not an entity instance we need to + # flatten the type declaration to something that + # maps to the python types + type_wrappers += (type_declaration,) + + while isinstance(attr_type, type_wrappers): + attr_type = attr_type.declared_type() + + if isinstance(attr_type, simple_type): + invalid = type(val) != simple_type_python_mapping[attr_type.declared_type()] + elif isinstance(attr_type, (entity_type, type_declaration)): + invalid = not isinstance(val, ifcopenshell.entity_instance) or not val.is_a(attr_type.name()) + elif isinstance(attr_type, select_type): + invalid = not any(try_valid(x, val) for x in attr_type.select_list()) + elif isinstance(attr_type, enumeration_type): + invalid = val not in attr_type.enumeration_items() + elif isinstance(attr_type, aggregation_type): + b1, b2 = attr_type.bound1(), attr_type.bound2() + ty = attr_type.type_of_element() + invalid = len(val) < b1 or (b2 != -1 and len(val) > b2) or not all(assert_valid(ty, v) for v in val) + else: + raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type)) + + if invalid: + raise ValidationError("%r not valid for %s" % (val, attr)) + + return True + +def try_valid(attr, val): + try: + return assert_valid(attr, val) + except ValidationError as e: + return False + +def validate(f): + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema) + for inst in f: + entity = schema.declaration_by_name(inst.is_a()) + + for attr, val, is_derived in zip(entity.all_attributes(), inst, entity.derived()): + + if val is None and not (is_derived or attr.optional()): + raise Exception("Attribute %s.%s not optional" % (entity, attr)) + + if val is not None: + attr_type = attr.type_of_attribute() + try: + assert_valid(attr, val) + except ValidationError as e: + print("In", inst) + print(e) + print() + + for attr in entity.all_inverse_attributes(): + val = getattr(inst, attr.name()) + assert_valid_inverse(attr, val) + +if __name__ == "__main__": + import sys + + for fn in sys.argv[1:]: + print("Validating", fn) + validate(ifcopenshell.open(fn))