def __setitem__(self, key, value): '''Set attribute *value* for *key*.''' attribute = self.__class__.attributes.get(key) if attribute is None: raise KeyError(key) attribute.set_local_value(self, value)
def __delitem__(self, key): '''Clear attribute value for *key*. .. note:: Will not remove the attribute, but instead clear any local value and revert to the last known server value. ''' attribute = self.__class__.attributes.get(key) attribute.set_local_value(self, ftrack_api.symbol.NOT_SET)
def _construct(self, data): '''Construct from *data*.''' # Suspend operation recording so that all modifications can be applied # in single create operation. In addition, recording a modification # operation requires a primary key which may not be available yet. relational_attributes = dict() with self.session.operation_recording(False): # Set defaults for any unset local attributes. for attribute in self.__class__.attributes: if attribute.name not in data: default_value = attribute.default_value if callable(default_value): default_value = default_value(self) attribute.set_local_value(self, default_value) # Data represents locally set values. for key, value in list(data.items()): if key in self._ignore_data_keys: continue attribute = self.__class__.attributes.get(key) if attribute is None: self.logger.debug( L( 'Cannot populate {0!r} attribute as no such ' 'attribute found on entity {1!r}.', key, self)) continue if not isinstance(attribute, ftrack_api.attribute.ScalarAttribute): relational_attributes.setdefault(attribute, value) else: attribute.set_local_value(self, value) # Record create operation. # Note: As this operation is recorded *before* any Session.merge takes # place there is the possibility that the operation will hold references # to outdated data in entity_data. However, this would be unusual in # that it would mean the same new entity was created twice and only one # altered. Conversely, if this operation were recorded *after* # Session.merge took place, any cache would not be able to determine # the status of the entity, which could be important if the cache should # not store newly created entities that have not yet been persisted. Out # of these two 'evils' this approach is deemed the lesser at this time. # A third, more involved, approach to satisfy both might be to record # the operation with a PENDING entity_data value and then update with # merged values post merge. if self.session.record_operations: entity_data = {} # Lower level API used here to avoid including any empty # collections that are automatically generated on access. for attribute in self.attributes: value = attribute.get_local_value(self) if value is not ftrack_api.symbol.NOT_SET: entity_data[attribute.name] = value self.session.recorded_operations.push( ftrack_api.operation.CreateEntityOperation( self.entity_type, ftrack_api.inspection.primary_key(self), entity_data)) for attribute, value in list(relational_attributes.items()): # Finally we set values for "relational" attributes, we need # to do this at the end in order to get the create operations # in the correct order as the newly created attributes might # contain references to the newly created entity. attribute.set_local_value(self, value)
def merge(self, entity, merged=None): '''Merge *entity* attribute values and other data into this entity. Only merge values from *entity* that are not :attr:`ftrack_api.symbol.NOT_SET`. Return a list of changes made with each change being a mapping with the keys: * type - Either 'remote_attribute', 'local_attribute' or 'property'. * name - The name of the attribute / property modified. * old_value - The previous value. * new_value - The new merged value. ''' log_debug = self.logger.isEnabledFor(logging.DEBUG) if merged is None: merged = {} log_message = 'Merged {type} "{name}": {old_value!r} -> {new_value!r}' changes = [] # Attributes. # Prioritise by type so that scalar values are set first. This should # guarantee that the attributes making up the identity of the entity # are merged before merging any collections that may have references to # this entity. attributes = collections.deque() for attribute in entity.attributes: if isinstance(attribute, ftrack_api.attribute.ScalarAttribute): attributes.appendleft(attribute) else: attributes.append(attribute) for other_attribute in attributes: attribute = self.attributes.get(other_attribute.name) # Local attributes. other_local_value = other_attribute.get_local_value(entity) if other_local_value is not ftrack_api.symbol.NOT_SET: local_value = attribute.get_local_value(self) if local_value != other_local_value: merged_local_value = self.session.merge(other_local_value, merged=merged) attribute.set_local_value(self, merged_local_value) changes.append({ 'type': 'local_attribute', 'name': attribute.name, 'old_value': local_value, 'new_value': merged_local_value }) log_debug and self.logger.debug( log_message.format(**changes[-1])) # Remote attributes. other_remote_value = other_attribute.get_remote_value(entity) if other_remote_value is not ftrack_api.symbol.NOT_SET: remote_value = attribute.get_remote_value(self) if remote_value != other_remote_value: merged_remote_value = self.session.merge( other_remote_value, merged=merged) attribute.set_remote_value(self, merged_remote_value) changes.append({ 'type': 'remote_attribute', 'name': attribute.name, 'old_value': remote_value, 'new_value': merged_remote_value }) log_debug and self.logger.debug( log_message.format(**changes[-1])) # We need to handle collections separately since # they may store a local copy of the remote attribute # even though it may not be modified. if not isinstance( attribute, ftrack_api.attribute.AbstractCollectionAttribute): continue local_value = attribute.get_local_value(self) # Populated but not modified, update it. if (local_value is not ftrack_api.symbol.NOT_SET and local_value == remote_value): attribute.set_local_value(self, merged_remote_value) changes.append({ 'type': 'local_attribute', 'name': attribute.name, 'old_value': local_value, 'new_value': merged_remote_value }) log_debug and self.logger.debug( log_message.format(**changes[-1])) return changes