Files
IfcOpenShell/src/ifcopenshell-python/ifcopenshell/validate.py
T

155 lines
5.3 KiB
Python
Raw Normal View History

2019-04-03 14:41:25 +02:00
from __future__ import print_function
import sys
import json
import functools
from collections import namedtuple
2019-04-03 14:41:25 +02:00
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
2020-08-30 15:21:21 +02:00
log_entry_type = namedtuple('log_entry_type', ("level", "message", "instance"))
class json_logger:
def __init__(self):
self.statements = []
2020-08-30 15:21:21 +02:00
self.instance = None
2020-08-30 15:21:21 +02:00
def set_instance(self, instance):
self.instance = instance
2020-08-30 15:21:21 +02:00
def log(self, level, message, instance):
self.statements.append(log_entry_type(level, message, instance)._asdict())
def __getattr__(self, level):
2020-08-30 15:21:21 +02:00
return functools.partial(self.log, level, instance=self.instance)
2019-04-03 14:41:25 +02:00
simple_type_python_mapping = {
2019-10-22 10:49:07 +02:00
# @todo should include unicode for Python2
2019-04-03 14:41:25 +02:00
"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
2020-03-30 12:03:28 +11:00
def validate(f, logger):
2019-04-03 14:41:25 +02:00
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema)
for inst in f:
2020-08-30 15:21:21 +02:00
if hasattr(logger, 'set_instance'):
logger.set_instance(inst)
2019-04-03 14:41:25 +02:00
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()):
2020-03-30 12:46:09 +11:00
logger.error("Attribute %s.%s not optional" % (entity, attr))
2019-04-03 14:41:25 +02:00
if val is not None:
attr_type = attr.type_of_attribute()
try:
assert_valid(attr, val)
except ValidationError as e:
2020-08-30 15:21:21 +02:00
if hasattr(logger, 'set_instance'):
logger.error(str(e))
else:
logger.error('In {}\n{}'.format(inst, e))
2019-04-03 14:41:25 +02:00
for attr in entity.all_inverse_attributes():
val = getattr(inst, attr.name())
2020-03-30 12:03:28 +11:00
try:
assert_valid_inverse(attr, val)
except ValidationError as e:
2020-08-30 15:21:21 +02:00
if hasattr(logger, 'set_instance'):
logger.error(str(e))
else:
logger.error('In {}\n{}'.format(inst, e))
2020-03-30 12:03:28 +11:00
2019-04-03 14:41:25 +02:00
if __name__ == "__main__":
import sys
2020-03-30 12:03:28 +11:00
import logging
2019-04-03 14:41:25 +02:00
filenames = [x for x in sys.argv[1:] if not x.startswith('--')]
flags = set(x for x in sys.argv[1:] if x.startswith('--'))
for fn in filenames:
if '--json' in flags:
logger = json_logger()
else:
logger = logging.getLogger('validate')
logger.setLevel(logging.DEBUG)
f = ifcopenshell.open(fn)
print("Validating", fn, file=sys.stderr)
validate(f, logger)
if '--json' in flags:
print("\n".join(json.dumps(x, default=str) for x in logger.statements))