Beispiel #1
0
    def get(cls, _id, select=None) -> Optional[Union['Resource', 'Batch']]:
        if _id is None:
            return None

        if is_sequence(_id):
            return cls.get_many(_ids=_id, select=select)

        if not select:
            select = set(cls.ravel.schema.fields.keys())
        elif not isinstance(select, set):
            select = set(select)

        select |= {ID, REV}
        select -= cls.ravel.virtual_fields.keys()

        cls.on_get(_id, select)

        state = cls.ravel.local.store.dispatch('fetch', (_id, ),
                                               {'fields': select})

        resource = cls(state=state).clean() if state else None

        cls.post_get(resource)

        return resource
Beispiel #2
0
    def fset(self, resource: 'Resource', new_value):
        """
        Set resource state data, calling the resolver's on_set callbak.
        """
        resolver = self.resolver
        target_class = self.resolver.target
        target_schema = target_class.ravel.schema
        old_value = resource.internal.state.get(resolver.name)

        # cast plain lists and dicts to target Resource or Batch objects
        if self.resolver.many:
            if not is_batch(new_value):
                assert is_sequence(new_value)
                new_value = self.resolver.target.Batch(
                    target_class(x) if isinstance(x, dict) else x
                    for x in new_value)
        elif isinstance(new_value, dict) \
                and resolver.name not in target_schema.fields:
            new_value = self.resolver.target(new_value)

        # write new value to instance state
        resource.internal.state[resolver.name] = new_value

        # trigger on_set callback
        resolver.on_set(resource, old_value, new_value)
Beispiel #3
0
    def clean(self, fields=None) -> 'Resource':
        if fields:
            fields = fields if is_sequence(fields) else {fields}
            keys = self._normalize_selectors(fields)
        else:
            keys = set(self.ravel.resolvers.keys())

        if keys:
            self.internal.state.clean(keys=keys)

        return self
Beispiel #4
0
    def mark(self, fields=None) -> 'Resource':
        # TODO: rename "mark" method to "touch"
        if fields is not None:
            if not fields:
                return self
            fields = fields if is_sequence(fields) else {fields}
            keys = self._normalize_selectors(fields)
        else:
            keys = set(self.Schema.fields.keys())

        self.internal.state.mark(keys)
        return self
Beispiel #5
0
    def exists_many(cls, entity: 'Entity') -> bool:
        """
        Does a simple check if a Resource exists by id.
        """
        store = cls.ravel.local.store

        if not entity:
            return False

        if is_batch(entity):
            args = (entity._id, )
        else:
            assert is_sequence(entity)
            id_list = entity
            args = (id_list, )
            for id_value in id_list:
                value, errors = cls._id.resolver.field.process(id_value)
                if errors:
                    raise ValueError(str(errors))

        return store.dispatch('exists_many', args=args)
Beispiel #6
0
    def save_many(cls,
                  resources: List['Resource'],
                  resolvers: Union[Text, Set[Text]] = None,
                  depth: int = 0) -> 'Batch':
        """
        Essentially a bulk upsert.
        """
        def seems_created(resource):
            return ((ID in resource.internal.state)
                    and (ID not in resource.internal.state.dirty))

        if resolvers is not None:
            if isinstance(resolvers, str):
                resolvers = {resolvers}
            elif not isinstance(resolvers, set):
                resolvers = set(resolvers)
            fields_to_save = set()
            resolvers_to_save = set()
            for k in resolvers:
                if k in cls.ravel.schema.fields:
                    fields_to_save.add(k)
                else:
                    resolvers_to_save.add(k)
        else:
            fields_to_save = None
            resolvers_to_save = set()

        # partition resources into those that are "uncreated" and those which
        # simply need to be updated.
        to_update = []
        to_create = []
        for resource in resources:
            # TODO: merge duplicates
            if not seems_created(resource):
                to_create.append(resource)
            else:
                to_update.append(resource)

        # perform bulk create and update
        if to_create:
            cls.create_many(to_create, fields=fields_to_save)
        if to_update:
            cls.update_many(to_update, fields=fields_to_save)

        retval = cls.Batch(to_update + to_create)

        if depth < 1:
            # base case. do not recurse on Resolvers
            return retval

        # aggregate and save all Resources referenced by all objects in
        # `resource` via their resolvers.
        class_2_objects = defaultdict(set)
        resolvers = cls.ravel.resolvers.by_tag('fields', invert=True)
        for resolver in resolvers.values():
            if resolver.name not in resolvers_to_save:
                continue
            for resource in resources:
                if resolver.name in resource.internal.state:
                    value = resource.internal.state[resolver.name]
                    resolver.on_save(resolver, resource, value)
                    if value:
                        if is_resource(value):
                            class_2_objects[resolver.owner].add(value)
                        else:
                            assert is_sequence(value)
                            class_2_objects[resolver.owner].update(value)
                    elif value is None and not resolver.nullable:
                        raise ValidationError(
                            f'{get_class_name(cls)}.{resolver.name} '
                            f'is required by save')

        # recursively call save_many for each type of Resource
        for resource_type, resources in class_2_objects.items():
            resource_type.save_many(resources, depth=depth - 1)

        return retval