开发者

Expunge object from SQLAlchemy session

开发者 https://www.devze.com 2023-01-17 22:00 出处:网络
I want to pass an instance of a mapped class to a non-SQLAlchemy aware method (in another process) and only n开发者_开发百科eed the values of my attributes. The problem is, that an UnboundExecutionErr

I want to pass an instance of a mapped class to a non-SQLAlchemy aware method (in another process) and only n开发者_开发百科eed the values of my attributes. The problem is, that an UnboundExecutionError occurs, every time the method wants to read an attribute value. I do understand, why this happens, but I would like to have a solution for this problem.

I only need the values of my defined attributes (id, name and dirty in the example) and do not need the SQLAlchemy overhead in the destination method.

Example class:

Base = declarative_base()
class Record(Base):
    __tablename__ = 'records'
    _id = Column('id', Integer, primary_key=True)
    _name = Column('name', String(50))
    _dirty = Column('dirty', Boolean, index=True)

    @synonym_for('_id')
    @property
    def id(self):
        return self._id

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value
        self._dirty = True

    name = synonym('_name', descriptor=name)

    @synonym_for('_dirty')
    @property
    def dirty(self):
        return self._dirty

Example call:

...
def do_it(self):
    records = self.query.filter(Record.dirty == True)
    for record in records:
        pass_to_other_process(record)

I've tried using session.expunge() and copy.copy(), but without success.


You need to remove the SQL ALchemy object from the session aka 'expunge' it. Then you can request any already loaded attribute w/o it attempting to reuse its last known session/unbound session.

self.expunge(record)

Be aware though, that any unloaded attribute will return it's last known value or None. If you would like to later work with the object again you can just call 'add' again or 'merge'

self.add(record)


My guess is that you're running afoul of SQLAlchemy's lazy loading. Since I don't actually know a whole lot about SQLAlchemy's internals, here's what I recommend:

class RecordData(object):
    __slots__ = ('id', 'name', 'dirty')

    def __init__(self, rec):
        self.id = rec.id
        self.name = rec.name
        self.dirty = rec.dirty

Then later on...

def do_it(self):
    records = self.query.filter(Record.dirty == True)
    for record in records:
        pass_to_other_process(RecordData(record))

Now, I think there is a way to tell SQLAlchemy to turn your object into a 'dumb' object that has no connection to the database and looks very much like what I just made here. But I don't know what it is.


You may need to make the object transient as well:

This describes one of the major object states which an object can have within a Session; a transient object is a new object that doesn’t have any database identity and has not been associated with a session yet. When the object is added to the session, it moves to the pending state.

You can make that by expunging the old object, making it transient and then using it to create the new object.

Something like:

# Get the original object (new query style)
query = select(MyTable).where(MyTable.id == old_object_id)
results = session.execute(query)
old_object = result.scalar_one_or_none()

# Remove it from the session & make it transient
session.expunge(old_object)
make_transient(old_object)

# Use it to create the new object
new_object = MyTable(attr1=old_object.attr1 ...)

# Add & commit the new object
session.add(new_object)
session.commit()

0

精彩评论

暂无评论...
验证码 换一张
取 消