A bunch of not so pretty validate.py performance improvements

This commit is contained in:
Thomas Krijnen
2022-10-04 15:16:34 +02:00
parent 12aa66fdcb
commit fa6bbf2d4a
@@ -135,13 +135,28 @@ def assert_valid_inverse(attr, val, schema):
) )
return True return True
select_members_cache = {}
def get_select_members(schema, ty):
cache_key = schema.name(), ty.name()
from_cache = select_members_cache.get(cache_key)
if from_cache:
return from_cache
def assert_valid(attr, val, schema): def inner(ty):
if isinstance(attr, attribute): if isinstance(ty, select_type):
attr_type = attr.type_of_attribute() for st in ty.select_list():
else: yield from inner(st)
attr_type = attr elif isinstance(ty, entity_type):
yield ty.name()
for st in ty.subtypes():
yield from inner(st)
elif isinstance(ty, type_declaration):
yield ty.name()
v = select_members_cache[cache_key] = set(inner(ty))
return v
def assert_valid(attr_type, val, schema, no_throw=False, attr=None):
type_wrappers = (named_type,) type_wrappers = (named_type,)
if not isinstance(val, ifcopenshell.entity_instance): if not isinstance(val, ifcopenshell.entity_instance):
# If val is not an entity instance we need to # If val is not an entity instance we need to
@@ -172,9 +187,13 @@ def assert_valid(attr, val, schema):
else: else:
invalid = True invalid = True
if not invalid: if not invalid:
invalid = not any( # Previously we relied on `is_a(x) for x in attr_type.select_items()`
try_valid(x, val_to_use, schema) for x in attr_type.select_list() # this was linear in the number of select leafs, which is very large
) # for e.g IfcValue, which is an often used select. Therefore, we now
# calculate (and cache) the select leafs (including entity subtypes)
# for the select definition and simply check for membership in this
# set.
invalid = val_to_use.is_a() not in get_select_members(schema, attr_type)
elif isinstance(attr_type, enumeration_type): elif isinstance(attr_type, enumeration_type):
invalid = val not in attr_type.enumeration_items() invalid = val not in attr_type.enumeration_items()
elif isinstance(attr_type, aggregation_type): elif isinstance(attr_type, aggregation_type):
@@ -188,19 +207,14 @@ def assert_valid(attr, val, schema):
else: else:
raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type)) raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type))
if invalid: if no_throw:
return not invalid
elif invalid:
raise ValidationError( raise ValidationError(
f"With attribute:\n {attr}\nValue:\n {val}\nNot valid\n" f"With attribute:\n {attr or attr_type}\nValue:\n {val}\nNot valid\n"
) )
else:
return True return True
def try_valid(attr, val, schema):
try:
return assert_valid(attr, val, schema)
except ValidationError as e:
return False
def log_internal_cpp_errors(filename, logger): def log_internal_cpp_errors(filename, logger):
@@ -235,6 +249,21 @@ def log_internal_cpp_errors(filename, logger):
else: else:
logger.error("For instance:\n %s\n%s", line, m) logger.error("For instance:\n %s\n%s", line, m)
entity_attribute_map = {}
def get_entity_attributes(schema, entity):
cache_key = schema.name(), entity
from_cache = entity_attribute_map.get(cache_key)
if from_cache:
return from_cache
entity_attrs = (
ent := schema.declaration_by_name(entity),
ent.all_attributes(),
)
entity_attribute_map[cache_key] = entity_attrs
return entity_attrs
def validate(f, logger): def validate(f, logger):
""" """
@@ -273,8 +302,7 @@ def validate(f, logger):
if hasattr(logger, "set_instance"): if hasattr(logger, "set_instance"):
logger.set_instance(inst) logger.set_instance(inst)
entity = schema.declaration_by_name(inst.is_a()) entity, attrs = get_entity_attributes(schema, inst.is_a())
attrs = entity.all_attributes()
if entity.is_abstract(): if entity.is_abstract():
e = "Entity %s is abstract" % entity.name() e = "Entity %s is abstract" % entity.name()
@@ -284,9 +312,10 @@ def validate(f, logger):
logger.error("For instance:\n %s\n%s", inst, e) logger.error("For instance:\n %s\n%s", inst, e)
has_invalid_value = False has_invalid_value = False
values = [None] * len(attrs)
for i in range(len(attrs)): for i in range(len(attrs)):
try: try:
inst[i] values[i] = inst[i]
pass pass
except: except:
if hasattr(logger, "set_instance"): if hasattr(logger, "set_instance"):
@@ -303,7 +332,7 @@ def validate(f, logger):
if not has_invalid_value: if not has_invalid_value:
for i, (attr, val, is_derived) in enumerate( for i, (attr, val, is_derived) in enumerate(
zip(attrs, inst, entity.derived()) zip(attrs, values, entity.derived())
): ):
if val is None and not (is_derived or attr.optional()): if val is None and not (is_derived or attr.optional()):
@@ -320,7 +349,7 @@ def validate(f, logger):
if val is not None: if val is not None:
attr_type = attr.type_of_attribute() attr_type = attr.type_of_attribute()
try: try:
assert_valid(attr, val, schema) assert_valid(attr_type, val, schema, attr=attr)
except ValidationError as e: except ValidationError as e:
if hasattr(logger, "set_instance"): if hasattr(logger, "set_instance"):
logger.error(str(e)) logger.error(str(e))