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)
def _recurse_on_entity(self, entity: 'Entity', links: Dict): if is_batch(entity): rel_resources = entity else: rel_resources = [entity] for rel_resource in rel_resources: self._dump_recursive(rel_resource, links)
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
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
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)
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
def __init__( self, target=None, name=None, owner=None, decorator=None, on_resolve=None, on_resolve_batch=None, on_select=None, on_save=None, on_set=None, on_get=None, on_delete=None, on_dump=None, private=False, nullable=True, required=False, immutable=False, many=False, ): self.name = name self.owner = owner self.private = private self.nullable = nullable self.decorator = decorator self.required = required self.target_callback = None self.target = None self.schema = None self.many = many self.immutable = immutable self.on_resolve = on_resolve or self.on_resolve self.on_resolve_batch = on_resolve_batch or self.on_resolve_batch self.on_select = on_select or self.on_select self.on_save = on_save or self.on_save self.on_dump = on_dump or self.on_dump self.on_get = on_get or self.on_get self.on_set = on_set or self.on_set self.on_delete = on_delete or self.on_delete if is_resource_type(target): self.target = target self.many = False elif is_batch(target): self.target = target.owner self.many = True elif target is not None: assert callable(target) self.target_callback = target
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
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
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)
def _execute_requests(self, query, resources, requests): for request in requests: request.query = query resolver = request.resolver # first try resolving in batch if len(resources) > 1: results = resolver.resolve_batch(resources, request) if results: for resource in resources: value = results.get(resource) if resolver.many and value is None: value = self.target.Batch() elif (not resolver.many) and is_batch(value): value = None resource.internal.state[resolver.name] = value continue # default on resolving for each resource separatesly for resource in resources: value = resolver.resolve(resource, request) resource.internal.state[resolver.name] = value
def default(self, target): if is_resource(target) or is_batch(target): return target.dump() else: return super().default(target)