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 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)
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 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)
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 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)
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 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
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
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
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
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)
def default(self, target): if is_resource(target) or is_batch(target): return target.dump() else: return super().default(target)