def __rollback(self, state): source_entity = state.entity ent_cls = type(source_entity) status = state.status if status != ENTITY_STATUS.CLEAN: mongo_coll = self.__get_mongo_collection(ent_cls) ent_oid = getattr(source_entity, '_id') if status == ENTITY_STATUS.NEW: mongo_coll.remove(ent_oid) if __debug__: self.__logger.debug("Rollback INSERT entity OID %s." % ent_oid) else: if status == ENTITY_STATUS.DELETED: data = transform_incoming(ent_cls, source_entity) mongo_coll.insert(data) if __debug__: self.__logger.debug('Rollback REMOVE entity OID %s: ' '%s.' % (ent_oid, data.items())) else: assert status == ENTITY_STATUS.DIRTY EntityState.set_state_data(source_entity, state.clean_data) data = transform_incoming(ent_cls, source_entity) mongo_coll.update({'_id':ent_oid}, data) if __debug__: self.__logger.debug('Rollback UPDATE entity OID %s: ' '%s.' % (ent_oid, data.items()))
def unregister(self, entity_class, entity): """ Unregisters the given entity for the given class and discards its state information. """ EntityState.release(entity, self) self.__entity_set_map[entity_class].remove(entity)
def update(self, source_data, target_entity): """ Updates the state of the target entity with the given source data. :param target_entity: Entity to update. :type target_entity: Object implementing :class:`everest.interfaces.IEntity`. """ EntityState.set_state_data(target_entity, source_data)
def register_clean(self, entity_class, entity): """ Registers the given entity for the given class as CLEAN. :returns: Cloned entity. """ EntityState.manage(entity, self) EntityState.get_state(entity).status = ENTITY_STATUS.CLEAN self.__entity_set_map[entity_class].add(entity)
def reset(self): """ Releases all entities held by this Unit Of Work (i.e., removes state information from all registered entities and clears the entity map). """ for ents in self.__entity_set_map.values(): for ent in ents: EntityState.release(ent, self) self.__entity_set_map.clear()
def register_deleted(self, entity_class, entity): """ Registers the given entity for the given class as DELETED. :raises ValueError: If the given entity already holds state that was created by another Unit Of Work. """ EntityState.manage(entity, self) EntityState.get_state(entity).status = ENTITY_STATUS.DELETED self.__entity_set_map[entity_class].add(entity)
def mark_deleted(self, entity): """ Marks the given entity as DELETED. :raises ValueError: If the given entity does not hold state. """ EntityState.get_state(entity).status = ENTITY_STATUS.DELETED
def mark_dirty(self, entity): """ Marks the given entity for the given class as DIRTY. :raises ValueError: If the given entity does not hold state. """ EntityState.get_state(entity).status = ENTITY_STATUS.DIRTY
def __clone(self, entity, cache): clone = object.__new__(entity.__class__) # We add the clone with its ID set to the cache *before* we load it # so that circular references will work. clone.id = entity.id cache.add(clone) state = EntityState.get_state_data(entity) id_attr = None for attr, value in iteritems_(state): if attr.entity_attr == 'id': id_attr = attr continue attr_type = attr.attr_type if attr.kind != RESOURCE_ATTRIBUTE_KINDS.TERMINAL \ and not self.__repository.is_registered_resource(attr_type): # Prevent loading of entities from other repositories. # FIXME: Doing this here is inconsistent, since e.g. the RDB # session does not perform this kind of check. continue elif attr.kind == RESOURCE_ATTRIBUTE_KINDS.MEMBER \ and not value is None: ent_cls = get_entity_class(attr_type) new_value = self.load(ent_cls, value) state[attr] = new_value elif attr.kind == RESOURCE_ATTRIBUTE_KINDS.COLLECTION \ and len(value) > 0: value_type = type(value) new_value = value_type.__new__(value_type) if issubclass(value_type, MutableSequence): add_op = new_value.append elif issubclass(value_type, MutableSet): add_op = new_value.add else: raise ValueError('Do not know how to clone value of type ' '%s for resource attribute %s.' % (type(new_value), attr)) ent_cls = get_entity_class(attr_type) for child in value: child_clone = self.load(ent_cls, child) add_op(child_clone) state[attr] = new_value # We set the ID already above. if not id_attr is None: del state[id_attr] EntityState.set_state_data(clone, state) return clone
def mark_new(self, entity): """ Marks the given entity as NEW. This is done when an entity is re-associated with a session after having been removed before. """ EntityState.get_state(entity).status = ENTITY_STATUS.NEW
def mark_pending(self, entity): """ Sets the flag indicating if the state of the given entity has been persisted to `False`. :note: The persistency flag is orthogonal to the status flag. """ EntityState.get_state(entity).is_persisted = False
def is_marked_pending(self, entity): """ Checks if the flag indicating that the state for the given entity has been persisted is `False`. :note: Use only after checking if the given entity is registered. :raises ValueError: If the entity has no state information. """ return not EntityState.get_state(entity).is_persisted
def __object_iterator(self, status, ent_cls): if ent_cls is None: ent_clss = self.__entity_set_map.keys() else: ent_clss = [ent_cls] for ent_cls in ent_clss: for ent in self.__entity_set_map[ent_cls]: if EntityState.get_state(ent).status == status: yield ent
def iterator(self): """ Returns an iterator over all entity states held by this Unit Of Work. """ # FIXME: There is no dependency tracking; objects are iterated in # random order. for ent_cls in list(self.__entity_set_map.keys()): for ent in self.__entity_set_map[ent_cls]: yield EntityState.get_state(ent)
def mark_clean(self, entity): """ Marks the given entity as CLEAN. This is done when an entity is loaded fresh from the repository or after a commit. """ state = EntityState.get_state(entity) state.status = ENTITY_STATUS.CLEAN state.is_persisted = True
def is_marked_new(self, entity): """ Checks if the given entity is marked with status NEW. Returns `False` if the entity has no state information. """ try: result = EntityState.get_state(entity).status == ENTITY_STATUS.NEW except ValueError: result = False return result
def is_marked_pending(self, entity): """ Checks if the flag indicating that the state for the given entity has been persisted is `False`. Returns `False` if the entity has no state information. """ try: result = not EntityState.get_state(entity).is_persisted except ValueError: result = False return result
def test_basics(self): ent = _MyEntity(id=0) self._uow.register_new(_MyEntity, ent) self.assert_equal(EntityState.get_state(ent).status, ENTITY_STATUS.NEW) self.assert_equal([item.entity for item in self._uow.iterator()], [ent]) self.assert_equal(list(self._uow.get_new(_MyEntity)), [ent]) self._uow.mark_clean(ent) self.assert_equal(list(self._uow.get_clean(_MyEntity)), [ent]) self._uow.mark_dirty(ent) self.assert_equal(list(self._uow.get_dirty(_MyEntity)), [ent]) self._uow.mark_deleted(ent) self.assert_equal(list(self._uow.get_deleted(_MyEntity)), [ent]) self._uow.unregister(_MyEntity, ent) self.assert_equal(list(self._uow.iterator()), []) self._uow.reset()
def test_state_data(self): data = dict(text='FOO', number=-1, parent=MyEntityParent(), children=[MyEntityChild()]) entity = MyEntity(**data) # We don't want to test the required unit of work here. uow = MagicMock() self.assert_raises(ValueError, EntityState.get_state, entity) entity.__everest__ = EntityState(entity, uow) state_data = EntityState.get_state(entity).data for attr, value in state_data.items(): if attr.resource_attr == 'number': number_attr = attr elif attr.resource_attr == 'parent': parent_attr = attr elif attr.resource_attr == 'parent_text': parent_text_attr = attr if attr.resource_attr in data: self.assert_equal(value, data[attr.resource_attr]) new_number = -2 state_data[number_attr] = new_number EntityState.get_state(entity).data = state_data self.assert_equal(entity.number, new_number) new_entity = MyEntity() self.assert_not_equal(new_entity.number, new_number) new_entity.__everest__ = EntityState(new_entity, uow) EntityState.transfer_state_data(entity, new_entity) self.assert_equal(new_entity.number, new_number) # Make setting invalid attribute fail. invalid_number_attr = terminal_attribute(str, 'grmbl') del state_data[number_attr] state_data[invalid_number_attr] = -2 with self.assert_raises(ValueError) as cm: EntityState.get_state(entity).data = state_data self.assert_true(cm.exception.args[0].startswith('Can not set')) # Make set nested attribute fail. entity.parent = None del state_data[invalid_number_attr] del state_data[parent_attr] state_data[parent_text_attr] = 'FOO PARENT' state = EntityState.get_state(entity) self.assert_raises(AttributeError, setattr, state, 'data', state_data)
def test_basics(self): ent = MyEntity(id=0) cache = EntityCache(entities=[]) cache.add(ent) assert cache.get_by_id(ent.id) is ent assert cache.has_id(ent.id) assert cache.get_by_slug(ent.slug)[0] is ent assert cache.has_slug(ent.slug) assert len(cache.get_all()) == 1 # Adding the same entity twice should not have any effect. cache.add(ent) assert cache.get_by_id(ent.id) is ent assert len(cache.get_all()) == 1 # ent1 = MyEntity(id=0) txt = 'FROBNIC' ent1.text = txt cache.update(EntityState.get_state_data(ent1), ent) assert cache.get_by_id(ent.id).text == txt assert cache.get_all() == [ent] assert list(cache.retrieve()) == [ent] cache.remove(ent) assert cache.get_by_id(ent.id) is None assert cache.get_by_slug(ent.slug) is None
def test_basics(self): ent = MyEntity(id=0) cache = EntityCache(entities=[]) cache.add(ent) self.assert_true(cache.get_by_id(ent.id) is ent) self.assert_true(cache.has_id(ent.id)) self.assert_true(cache.get_by_slug(ent.slug) is ent) self.assert_true(cache.has_slug(ent.slug)) self.assert_equal(len(cache.get_all()), 1) # Adding the same entity twice should not have any effect. cache.add(ent) self.assert_true(cache.get_by_id(ent.id) is ent) self.assert_equal(len(cache.get_all()), 1) # ent1 = MyEntity(id=0) txt = 'FROBNIC' ent1.text = txt cache.update(EntityState.get_state_data(ent1), ent) self.assert_equal(cache.get_by_id(ent.id).text, txt) self.assert_equal(cache.get_all(), [ent]) self.assert_equal(list(cache.retrieve()), [ent]) cache.remove(ent) self.assert_is_none(cache.get_by_id(ent.id)) self.assert_is_none(cache.get_by_slug(ent.slug))
def __update(self, source_data, target_entity, path): # pylint: disable=W0613 EntityState.set_state_data(target_entity, source_data)
def __update(self, source_data, target_entity): # pylint: disable=W0613 EntityState.set_state_data(target_entity, source_data) if self.__unit_of_work.is_marked_persisted(target_entity): self.__unit_of_work.mark_pending(target_entity)
def test_get_state_unregistered_fails(self): ent = _MyEntity() with self.assert_raises(ValueError) as cm: EntityState.get_state(ent) msg = 'Trying to obtain state for un-managed entity' self.assert_true(cm.exception.args[0].startswith(msg))