Example #1
0
    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)
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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)