From f41ad0d5de0a82848cf3fecb56b727195135f969 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 4 Oct 2022 10:21:45 +0200 Subject: [PATCH] #2471 work around file gc with instance refs --- .../ifcopenshell/entity_instance.py | 77 +++++++++++++++---- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py index c1869039da..c6a18b42ab 100644 --- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py +++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py @@ -107,20 +107,36 @@ class entity_instance(object): e = ifcopenshell_wrapper.new_IfcBaseClass(*e) super(entity_instance, self).__setattr__("wrapped_data", e) super(entity_instance, self).__setattr__("method_list", None) + + # Make sure the file is not gc'ed while we have live instances self.wrapped_data.file = file + def __del__(self): + """ + #2471 while the precise chain of action is unclear, creating + instance references prevents file gc, even with all instance + refs deleted. This is a work-around for that. + """ + self.wrapped_data.file = None + def __getattr__(self, name): INVALID, FORWARD, INVERSE = range(3) attr_cat = self.wrapped_data.get_attribute_category(name) if attr_cat == FORWARD: return entity_instance.wrap_value( - self.wrapped_data.get_argument(self.wrapped_data.get_argument_index(name)), self.wrapped_data.file + self.wrapped_data.get_argument( + self.wrapped_data.get_argument_index(name) + ), + self.wrapped_data.file, ) elif attr_cat == INVERSE: - return entity_instance.wrap_value(self.wrapped_data.get_inverse(name), self.wrapped_data.file) + return entity_instance.wrap_value( + self.wrapped_data.get_inverse(name), self.wrapped_data.file + ) else: raise AttributeError( - "entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(True), name) + "entity instance of type '%s' has no attribute '%s'" + % (self.wrapped_data.is_a(True), name) ) @staticmethod @@ -159,7 +175,11 @@ class entity_instance(object): :type attr: int :rtype: string """ - attr_idx = attr if isinstance(attr, numbers.Integral) else self.wrapped_data.get_argument_index(attr) + attr_idx = ( + attr + if isinstance(attr, numbers.Integral) + else self.wrapped_data.get_argument_index(attr) + ) return self.wrapped_data.get_argument_type(attr_idx) def attribute_name(self, attr_idx): @@ -177,15 +197,23 @@ class entity_instance(object): def __getitem__(self, key): if key < 0 or key >= len(self): - raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a())) - return entity_instance.wrap_value(self.wrapped_data.get_argument(key), self.wrapped_data.file) + raise IndexError( + "Attribute index {} out of range for instance of type {}".format( + key, self.is_a() + ) + ) + return entity_instance.wrap_value( + self.wrapped_data.get_argument(key), self.wrapped_data.file + ) def __setitem__(self, idx, value): if self.wrapped_data.file and self.wrapped_data.file.transaction: self.wrapped_data.file.transaction.store_edit(self, idx, value) if self.method_list is None: - super(entity_instance, self).__setattr__("method_list", _method_dict[self.is_a(True)]) + super(entity_instance, self).__setattr__( + "method_list", _method_dict[self.is_a(True)] + ) method = self.method_list[idx] @@ -193,7 +221,9 @@ class entity_instance(object): if method is not set_derived_atribute: self.wrapped_data.setArgumentAsNull(idx) else: - self.method_list[idx](self.wrapped_data, idx, entity_instance.unwrap_value(value)) + self.method_list[idx]( + self.wrapped_data, idx, entity_instance.unwrap_value(value) + ) return value @@ -240,8 +270,11 @@ class entity_instance(object): if self.id(): return self.wrapped_data == other.wrapped_data else: - return (self.is_a(), self[0], self.wrapped_data.file_pointer()) == \ - (other.is_a(), other[0], other.wrapped_data.file_pointer()) + return (self.is_a(), self[0], self.wrapped_data.file_pointer()) == ( + other.is_a(), + other[0], + other.wrapped_data.file_pointer(), + ) def __hash__(self): # Proper entity instances have a stable identity by means of the numeric @@ -263,7 +296,9 @@ class entity_instance(object): ) ) - def get_info(self, include_identifier=True, recursive=False, return_type=dict, ignore=()): + def get_info( + self, include_identifier=True, recursive=False, return_type=dict, ignore=() + ): """Return a dictionary of the entity_instance's properties (Python and IFC) and their values. :param include_identifier: Whether or not to include the STEP numerical identifier @@ -293,7 +328,11 @@ class entity_instance(object): yield "id", self.id() yield "type", self.is_a() except BaseException: - logging.exception("unhandled exception while getting id / type info on {}".format(self)) + logging.exception( + "unhandled exception while getting id / type info on {}".format( + self + ) + ) for i in range(len(self)): try: if self.wrapped_data.get_attribute_names()[i] in ignore: @@ -316,16 +355,24 @@ class entity_instance(object): ignore=ignore, ) - attr_value = entity_instance.walk(is_instance, get_info_, attr_value) + attr_value = entity_instance.walk( + is_instance, get_info_, attr_value + ) yield self.attribute_name(i), attr_value except BaseException: - logging.exception("unhandled exception occurred setting attribute name for {}".format(self)) + logging.exception( + "unhandled exception occurred setting attribute name for {}".format( + self + ) + ) return return_type(_()) __dict__ = property(get_info) - def get_info_2(self, include_identifier=True, recursive=False, return_type=dict, ignore=()): + def get_info_2( + self, include_identifier=True, recursive=False, return_type=dict, ignore=() + ): assert include_identifier assert recursive assert return_type is dict