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
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 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
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
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 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