GiveMeFive
GiveMeFive

Reputation: 3

SqlAlchemy: State Management , how to persist an unserialize instance in session

simple code like:

import pickle, cPickle
from app import session, redis 

class MyObj(DeclarativeBase):
    @classmethod
    def get(cls,id):
       key = cls.__name__+":"+str(id)
       cached = redis.get(key)
       if cached:
          # unserialize to cls instance
          return cPickle.loads(cached)
       record = session.query(cls).filter(cls.id==id).one()
       if record:
          # serialize and store to redis
          redis.set(key, pickle.dumps(record))
       return record


# first time , a normal orm instance returned from session query (uncached)
obj = MyObj.get(1)

# but the next requests, get cached from redis.
# and i want to delete the record
delobj = MyObj.get(1)
session.delete(delobj)
session.commit()

it raise an error like "instance xxx is not persisted"

i tried session.add(delobj) before delete, the Pending state is True ( state = inspect(delobj) ), but it still doesn't work for deleting.

Upvotes: 0

Views: 3373

Answers (1)

Ilja Everilä
Ilja Everilä

Reputation: 52949

You need to merge cached instances to current session:

merge() transfers state from an outside object into a new or already existing instance within a session.

From examples:

An application is storing objects in an in-memory cache, shared by many Session objects simultaneously. merge() is used each time an object is retrieved from the cache to create a local copy of it in each Session which requests it. The cached object remains detached; only its state is moved into copies of itself that are local to individual Session objects.

In the caching use case, it’s common to use the load=False flag to remove the overhead of reconciling the object’s state with the database. There’s also a “bulk” version of merge() called merge_result() that was designed to work with cache-extended Query objects - see the section Dogpile Caching.

So:

   if cached:
      # unserialize to cls instance
      return session.merge(cPickle.loads(cached), load=False)

Remember to cache persisted objects of cls only, or you'll get the error:

InvalidRequestError: merge() with load=False option does not support objects transient (i.e. unpersisted) objects.  flush() all changes on mapped instances before merging with load=False.

A simple way to produce the aforementioned error:

obj = MyObj()
cached = pickle.dumps(obj)
obj2 = session.merge(pickle.loads(cached), load=False)
 ...
InvalidRequestError: merge() with load=False option does not support objects transient (i.e. unpersisted) objects.  flush() all changes on mapped instances before merging with load=False.

If on the other hand you first properly persist the object:

In [63]: obj = MyObj()

In [64]: session.add(obj)

In [65]: session.flush()

In [66]: obj.__dict__
Out[66]: 
{'id': 2,
 '_sa_instance_state': <sqlalchemy.orm.state.InstanceState at 0x7fe6f380cac8>}

In [67]: cached = pickle.dumps(obj)

In [68]: session.commit()

In [69]: obj2 = session.merge(pickle.loads(cached), load=False)

In [70]: obj2 in session
Out[70]: True

Upvotes: 1

Related Questions