Esempio n. 1
0
    def build(self, source) -> 'Query':
        query = self.right_loader_property.resolver.owner.select()

        if is_resource(source):
            source_value = getattr(source, self.left_field.name)
            if not source_value:
                console.debug(
                    message=(f'relationship {get_class_name(source)}.'
                             f'{self.relationship.name} aborting execution'
                             f'because {self.left_field.name} is None'),
                    data={
                        'resource': source._id,
                    })
                # NOTE: ^ if you don't want this, then clear the field from
                # source using source.clean(field_name)
                return None
            query.where(self.right_loader_property == source_value)
        else:
            assert is_batch(source)
            left_field_name = self.left_field.name
            source_values = {getattr(res, left_field_name) for res in source}
            if not source_values:
                console.warning(
                    message=(f'relationship {get_class_name(source)}.'
                             f'{self.relationship.name} aborting query '
                             f'because {self.left_field.name} is empty'),
                    data={
                        'resources': source._id,
                    })
                # NOTE: ^ if you don't want this, then clear the field from
                # source using source.clean(field_name)
                return None
            query.where(self.right_loader_property.including(source_values))

        return query
Esempio n. 2
0
def _dump_result_obj(obj):
    if is_resource(obj) or is_batch(obj):
        return obj.dump()
    elif isinstance(obj, (list, set, tuple)):
        return [_dump_result_obj(x) for x in obj]
    elif isinstance(obj, dict):
        return {k: _dump_result_obj(v) for k, v in obj.items()}
    else:
        return obj
Esempio n. 3
0
    def resolve(self, entity: 'Entity', request=None):
        if request is None:
            request = Request(self)

        if is_resource(entity):
            return self.resolve_resource(entity, request)
        else:
            assert is_batch(entity)
            return self.resolve_batch(entity, request)
Esempio n. 4
0
    def load_param(self, many: bool, resource_type: Type['Resource'],
                   preloaded):
        """
        Convert the given parameter "preloaded" into its corresponding
        Entity.

        - If the preloaded value is an ID, fetch the object.
        - If it is a list of IDs, return a Batch.
        - If it is a dict, replace it with a corresponding Resource instance.
        """
        if not resource_type:
            return preloaded
        elif not many:
            if is_resource(preloaded):
                return preloaded
            elif isinstance(preloaded, dict):
                if 'id' in preloaded:
                    preloaded[ID] = preloaded.pop('id')
                if 'rev' in preloaded:
                    preloaded[REV] = preloaded.pop('rev')
                self.preload_resolver_state(resource_type, preloaded)
                return resource_type(state=preloaded)
            elif ((preloaded and isinstance(preloaded, str))
                  and (preloaded[0] == '{' and preloaded[-1] == '}')):
                try:
                    data = self._app.json.decode(preloaded)
                    return resource_type(data)
                except ValueError:
                    return None
            else:
                return resource_type.get(_id=preloaded)
        elif is_sequence(preloaded):
            if isinstance(preloaded, set):
                preloaded = list(preloaded)
            if is_resource(preloaded[0]):
                return resource_type.Batch(resources=preloaded)
            elif isinstance(preloaded[0], dict):
                return resource_type.Batch(
                    resource_type(state=self.preload_resolver_state(
                        resource_type, record)).merge(_id=None, _rev=None)
                    for record in preloaded)
            else:
                return resource_type.get_many(_ids=preloaded)
Esempio n. 5
0
 def process_value(obj):
     if is_resource(obj):
         if obj._id is not None:
             return obj._id
         else:
             return obj.internal.state
     elif is_batch(obj):
         raise NotImplemented(
             'TODO: implement support for batch objects')
     else:
         return obj
Esempio n. 6
0
    def including(self, *others) -> Predicate:
        others = flatten_sequence(others)
        visited = set()
        deduplicated = []
        for obj in others:
            if is_resource(obj):
                if obj._id not in visited:
                    visited.add(obj._id)
                    deduplicated.append(obj._id)
            else:
                if obj not in visited:
                    visited.add(obj)
                    deduplicated.append(obj)

        return ConditionalPredicate(OP_CODE.INCLUDING, self, deduplicated)
Esempio n. 7
0
 def _dump_result_obj(self, obj) -> Union[Dict, List]:
     """
     Currently, GrpcService is intended to be used for internal
     service-to-service comm. This is why we "dump" Resources here by simply
     returning their internal state dicts.
     """
     if is_resource(obj):
         return obj.internal.state
     elif is_batch(obj):
         return [x.internal.state for x in obj]
     elif isinstance(obj, (list, set, tuple)):
         return [self._dump_result_obj(x) for x in obj]
     elif isinstance(obj, dict):
         return {k: self._dump_result_obj(v) for k, v in obj.items()}
     else:
         return obj
Esempio n. 8
0
    def exists(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_resource(entity):
            args = (entity._id, )
        else:
            id_value, errors = cls._id.resolver.field.process(entity)
            args = (id_value, )
            if errors:
                raise ValueError(str(errors))

        return store.dispatch('exists', args=args)
Esempio n. 9
0
    def _dump_recursive(self, parent_resource: 'Resource', keys: Set) -> Dict:

        if parent_resource is None:
            return None

        if keys:
            keys_to_dump = keys if isinstance(keys, set) else set(keys)
        else:
            keys_to_dump = parent_resource.internal.state.keys()

        record = {}
        for k in keys_to_dump:
            v = parent_resource.internal.state.get(k)
            resolver = parent_resource.ravel.resolvers.get(k)
            assert resolver is not None

            if resolver.private:
                continue

            if k in parent_resource.ravel.resolvers.relationships:
                # handle the dumping of Relationships specially
                rel = resolver
                if rel.many:
                    assert is_batch(v)
                    child_batch = v
                    record[k] = [
                        self.dump(child_resource)
                        for child_resource in child_batch
                    ]
                else:
                    if v is not None:
                        assert is_resource(v)
                    child_resource = v
                    record[k] = self.dump(child_resource)
            else:
                # dump non-Relationship state
                if k in {ID, REV}:
                    k = k[1:]
                record[k] = resolver.dump(self, v)

        return record
Esempio n. 10
0
    def deselect(self, *args):
        """
        Remove the given arguments from the query's requests dict.
        """
        args = flatten_sequence(args)
        keys = set()

        for obj in args:
            if isinstance(obj, str):
                # if obj is str, replace it with the corresponding resolver
                # property from the target Resource class.
                _obj = getattr(self.target, obj, None)
                if _obj is None:
                    raise ValueError(f'unknown resolver: {obj}')
                obj = _obj
            elif is_resource(obj):
                keys.update(obj.internal.state.keys())
                continue
            elif is_resource_type(obj):
                keys.update(obj.ravel.resolvers.keys())
                continue

            # build a resolver request
            request = None
            if isinstance(obj, LoaderProperty) and (obj.decorator is None):
                resolver_property = obj
                keys.add(resolver_property.resolver.name)
            elif isinstance(obj, ResolverProperty):
                resolver_property = obj
                keys.add(resolver_property.resolver.name)
            elif isinstance(obj, Request):
                request = obj
                keys.add(request.resolver.name)

        for key in keys:
            if key in self.requests:
                del self.requests[key]

        return self
Esempio n. 11
0
    def select(self, *args):
        args = flatten_sequence(args)

        for obj in args:
            if isinstance(obj, str):
                # if obj is str, replace it with the corresponding resolver
                # property from the target Resource class.
                _obj = getattr(self.target, obj, None)
                if _obj is None:
                    raise ValueError(f'unknown resolver: {obj}')
                obj = _obj
            elif is_resource(obj):
                self.select(obj.internal.state.keys())
                continue
            elif is_resource_type(obj):
                self.select(obj.ravel.resolvers.keys())
                continue

            # build a resolver request
            request = None
            if isinstance(obj, LoaderProperty) and (obj.decorator is None):
                resolver_property = obj
                request = Request(resolver_property.resolver)
            elif isinstance(obj, ResolverProperty):
                resolver_property = obj
                request = Request(resolver_property.resolver)
            elif isinstance(obj, Request):
                request = obj

            # finally set the request if one was generated
            if request:
                resolver_name = request.resolver.name
                if resolver_name not in self.target.ravel.virtual_fields:
                    if self.from_request is not None:
                        request.parent = self.from_request
                    self.requests[request.resolver.name] = request

        return self
Esempio n. 12
0
    def select(self, *args) -> 'Request':
        from ravel.resolver.resolvers.loader import LoaderProperty
        from ravel.resolver.resolver_property import ResolverProperty

        args = flatten_sequence(args)

        for obj in args:
            if isinstance(obj, str):
                # if obj is str, replace it with the corresponding resolver
                # property from the target Resource class.
                _obj = getattr(self.resolver.owner, obj, None)
                if _obj is None:
                    raise ValueError(f'unknown resolver: {obj}')
                obj = _obj
            elif is_resource(obj):
                self.select(obj.internal.state.keys())
                continue
            elif is_resource_type(obj):
                self.select(obj.ravel.resolvers.keys())
                continue

            # build a resolver request
            request = None
            if isinstance(obj, LoaderProperty) and (obj.decorator is None):
                resolver_property = obj
                request = Request(resolver_property.resolver)
            elif isinstance(obj, ResolverProperty):
                resolver_property = obj
                request = Request(resolver_property.resolver)
            elif isinstance(obj, Request):
                request = obj

            if request is not None:
                self.parameters.select.append(request)
            elif obj is not None:
                self.parameters.select.append(obj)

        return self
Esempio n. 13
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
Esempio n. 14
0
 def excluding(self, *others) -> Predicate:
     others = flatten_sequence(others)
     others = {obj._id if is_resource(obj) else obj for obj in others}
     return ConditionalPredicate(OP_CODE.EXCLUDING, self, others)
Esempio n. 15
0
 def default(self, target):
     if is_resource(target) or is_batch(target):
         return target.dump()
     else:
         return super().default(target)