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 = { # @todo should include unicode for Python2 "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, logger): 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()): logger.error("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: logger.error('In {}\n{}'.format(inst, e)) for attr in entity.all_inverse_attributes(): val = getattr(inst, attr.name()) try: assert_valid_inverse(attr, val) except ValidationError as e: logger.error('In {}\n{}'.format(inst, e)) if __name__ == "__main__": import sys import logging for fn in sys.argv[1:]: logger = logging.getLogger('validate') logger.setLevel(logging.DEBUG) print("Validating", fn) validate(ifcopenshell.open(fn), logger)