def copy_data(self, target: Union[CompoundValue, BoundCompoundValue]) -> None: """Copy data from a target Entity or compound-value. This first verifies that the target has a matching set of fields and then copies its data into ourself. To copy data into a nested compound field, the assignment operator can be used. """ import copy from efro.entity.util import have_matching_fields tvalue: CompoundValue if isinstance(target, CompoundValue): tvalue = target elif isinstance(target, BoundCompoundValue): tvalue = target.d_value else: raise TypeError( 'Target must be a CompoundValue or BoundCompoundValue') target_data = getattr(target, 'd_data', None) if target_data is None: raise ValueError('Target is not bound to data.') assert isinstance(self, CompoundValue) if not have_matching_fields(self, tvalue): raise ValueError( f"Fields for target {type(tvalue)} do not match ours" f" ({type(self)}); can't copy data.") self.d_data = copy.deepcopy(target_data)
def steal_data(self, target: EntityMixin) -> None: """Steal data from another entity. This is more efficient than copy_data, as data is moved instead of copied. However this leaves the target object in an invalid state, and it must no longer be used after this call. This can be convenient for entities to use to update themselves with the result of a database transaction (which generally return fresh entities). """ from efro.entity.util import have_matching_fields if not isinstance(target, EntityMixin): raise TypeError('EntityMixin is required.') assert isinstance(target, CompoundValue) assert isinstance(self, CompoundValue) if not have_matching_fields(self, target): raise ValueError( f"Fields for target {type(target)} do not match ours" f" ({type(self)}); can't steal data.") assert target.d_data is not None self.d_data = target.d_data # Make sure target blows up if someone tries to use it. # noinspection PyTypeHints target.d_data = None # type: ignore
def __eq__(self, other: Any) -> Any: from efro.entity.util import have_matching_fields # We can only be compared to other bound-compound-fields if not isinstance(other, BoundCompoundDictField): return NotImplemented # If our compound values have differing fields, we're unequal. if not have_matching_fields(self.d_field.d_value, other.d_field.d_value): return False # Ok our data schemas match; now just compare our data.. return self.d_data == other.d_data
def set_with_data(self, data: Any, value: Any, error: bool) -> Any: # If we were passed a BoundCompoundDictField, # simply convert it to a flat dict of BoundCompoundValue objects which # is what we work with natively here. if isinstance(value, BoundCompoundDictField): value = dict(value.items()) if not isinstance(value, dict): raise TypeError('CompoundDictField expected dict value on set.') # Allow assigning only from a sequence of our existing children. # (could look into expanding this to other children if we can # be sure the underlying data will line up; for example two # CompoundListFields with different child_field values should not # be inter-assignable. if (not all(isinstance(i, BoundCompoundValue) for i in value.values())): raise ValueError('CompoundDictField assignment must be a ' 'dict containing only BoundCompoundValues.') # Make sure the data all has the same CompoundValue type and # compare that type against ours once to make sure its fields match. # (this will not allow passing CompoundValues from multiple sources # but I don't know if that would ever come up..) first_value: Any = None for i, val in enumerate(value.values()): if i == 0: first_value = val.d_value # Do the full field comparison on the first value only.. if not have_matching_fields(val.d_value, self.d_value): raise ValueError( 'CompoundListField assignment must be a ' 'list containing matching CompoundValues.') else: # For all remaining values, just ensure they match the first. if val.d_value is not first_value: raise ValueError( 'CompoundListField assignment cannot contain ' 'multiple CompoundValue types as sources.') data[self.d_key] = self.filter_input( {key: val.d_data for key, val in value.items()}, error=error)