def save(self): self.logger.debug(".save()") db_session = DbSession() # Prevent expiration after the session is closed or object is made transient or disconnected db_session.expire_on_commit = False try: # No need to 'add', committing this class db_session.add(self) db_session.commit() # Keep it detached make_transient(self) make_transient_to_detached(self) except InvalidRequestError as e: self.logger.error( ".save() - Could not commit to {} table in database".format( self.__tablename__), exc_info=True) self.__cleanupDbSession(db_session, self.__class__.__name__) except Exception as e: db_session.rollback() self.logger.error( ".save() - Could not commit to {} table in database".format( self.__tablename__), exc_info=True) raise DbException( "Could not commit to {} table in database".format( self.__tablename__))
def get_config(): db_session = DbSession() # Prevent expiration after the session is closed or object is made transient or disconnected db_session.expire_on_commit = False ccm = None try: # Should be only one, return last modified ccm = db_session.query(ChargerConfigModel) \ .order_by(desc(ChargerConfigModel.modified_at)) \ .first() # Detach (not transient) from database, allows saving in other Threads # https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.make_transient_to_detached make_transient(ccm) make_transient_to_detached(ccm) except InvalidRequestError as e: ChargerConfigModel.__cleanupDbSession(db_session, ChargerConfigModel.__class__) except Exception as e: # Nothing to roll back ChargerConfigModel.logger.error( "Could not query from {} table in database".format( ChargerConfigModel.__tablename__), exc_info=True) raise DbException( "Could not query from {} table in database".format( ChargerConfigModel.__tablename__)) return ccm
def manufacture_persistent_object( session, specimen, values=None, primary_key=None): """Make an ORM-mapped object persistent in a Session without SQL. The persistent object is returned. If a matching object is already present in the given session, the specimen is merged into it and the persistent object returned. Otherwise, the specimen itself is made persistent and is returned. The object must contain a full primary key, or provide it via the values or primary_key parameters. The object is peristed to the Session in a "clean" state with no pending changes. :param session: A Session object. :param specimen: a mapped object which is typically transient. :param values: a dictionary of values to be applied to the specimen, in addition to the state that's already on it. The attributes will be set such that no history is created; the object remains clean. :param primary_key: optional tuple-based primary key. This will also be applied to the instance if present. """ state = inspect(specimen) mapper = state.mapper for k, v in values.items(): orm.attributes.set_committed_value(specimen, k, v) pk_attrs = [ mapper.get_property_by_column(col).key for col in mapper.primary_key ] if primary_key is not None: for key, value in zip(pk_attrs, primary_key): orm.attributes.set_committed_value( specimen, key, value ) for key in pk_attrs: if state.attrs[key].loaded_value is orm.attributes.NO_VALUE: raise ValueError("full primary key must be present") orm.make_transient_to_detached(specimen) if state.key not in session.identity_map: session.add(specimen) return specimen else: return session.merge(specimen, load=False)
def test_make_transient_to_detached(self): users, User = self.tables.users, self.classes.User mapper(User, users) sess = Session() u1 = User(id=1, name='test') sess.add(u1) sess.commit() sess.close() u2 = User(id=1) make_transient_to_detached(u2) assert 'id' in u2.__dict__ sess.add(u2) eq_(u2.name, "test")
def test_already_attached(self): User = self.classes.User users = self.tables.users mapper(User, users) s1 = Session() s2 = Session() u1 = User(id=1, name='u1') make_transient_to_detached(u1) # shorthand for actually persisting it s1.add(u1) assert_raises_message( sa.exc.InvalidRequestError, "Object '<User.*?>' is already attached to session", s2.add, u1) assert u1 not in s2 assert not s2.identity_map.keys()
def test_already_attached(self): User = self.classes.User users = self.tables.users mapper(User, users) s1 = Session() s2 = Session() u1 = User(id=1, name='u1') make_transient_to_detached(u1) # shorthand for actually persisting it s1.add(u1) assert_raises_message( sa.exc.InvalidRequestError, "Object '<User.*?>' is already attached to session", s2.add, u1 ) assert u1 not in s2 assert not s2.identity_map.keys()
def delete(self): db_session = DbSession() db_session.expire_on_commit = True try: db_session.delete(self) db_session.commit() # Keep it detached make_transient(self) make_transient_to_detached(self) except InvalidRequestError as e: self.__cleanupDbSession(db_session, self.__class__.__name__) except Exception as e: db_session.rollback() self.logger.error( "Could not delete from {} table in database".format( self.__tablename__), exc_info=True) raise DbException( "Could not delete from {} table in database".format( self.__tablename__))
def new_version(self, session): # our current identity key, which will be used on the "old" # version of us to emit an UPDATE. this is just for assertion purposes old_identity_key = inspect(self).key # make sure self.start / self.end are not expired self.id, self.start, self.end # turn us into an INSERT make_transient(self) # make the "old" version of us, which we will turn into an # UPDATE old_copy_of_us = self.__class__( id=self.id, start=self.start, end=self.end ) # turn old_copy_of_us into an UPDATE make_transient_to_detached(old_copy_of_us) # the "old" object has our old identity key (that we no longer have) assert inspect(old_copy_of_us).key == old_identity_key # now put it back in the session session.add(old_copy_of_us) # now update the 'end' - SQLAlchemy sees this as a PK switch old_copy_of_us.end = current_time() # fun fact! the new_version() routine is *not* called for # old_copy_of_us! because we are already in the before_flush() hook! # this surprised even me. I was thinking we had to guard against # it. Still might be a good idea to do so. self.start = current_time() self.end = current_time() + datetime.timedelta(days=2)
def new_version(self, session): # our current identity key, which will be used on the "old" # version of us to emit an UPDATE. this is just for assertion purposes old_identity_key = inspect(self).key # make sure self.start / self.end are not expired self.id, self.start, self.end # turn us into an INSERT make_transient(self) # make the "old" version of us, which we will turn into an # UPDATE old_copy_of_us = self.__class__(id=self.id, start=self.start, end=self.end) # turn old_copy_of_us into an UPDATE make_transient_to_detached(old_copy_of_us) # the "old" object has our old identity key (that we no longer have) assert inspect(old_copy_of_us).key == old_identity_key # now put it back in the session session.add(old_copy_of_us) # now update the 'end' - SQLAlchemy sees this as a PK switch old_copy_of_us.end = current_time() # fun fact! the new_version() routine is *not* called for # old_copy_of_us! because we are already in the before_flush() hook! # this surprised even me. I was thinking we had to guard against # it. Still might be a good idea to do so. self.start = current_time() self.end = current_time() + datetime.timedelta(days=2)