SQLAlchemy, get object not bound to a Session - python

I am trying to get an collection of objects out of a database and pass it to another process that is not connected to the database. My code looks like the one below but I keep getting:
sqlalchemy.exc.UnboundExecutionError: Instance <MyClass at 0x8db7fec> is not bound to a Session; attribute refresh operation cannot proceed
When I try to look at the elements of my list outside of the get_list() method.
def get_list (obj):
sesson = Session()
lst = session.query(MyClass).all()
session.close()
return lst
However, if I use this:
def get_list_bis (obj)
session = Session()
return session.query(MyClass).all()
I am able to use the elements but worry about the state of the session since it was not closed.
What am I missing here?

If you want a bunch of objects produced by querying a session to be usable outside the scope of the session, you need to expunge them for the session.
In your first function example, you will need to add a line:
session.expunge_all()
before
session.close()
More generally, let's say the session is not closed right away, like in the first example. Perhaps this is a session that is kept active during entire duration of a web request or something like that. In such cases, you don't want to do expunge_all. You will want to be more surgical:
for item in lst:
session.expunge(item)

This often happens due to objects being in expired state, objects get expired for example after committing, then when such expired objects are about to get used the ORM tries to refresh them, but this cannot be done when objects are detached from session (e.g. because that session was closed). This behavior can be managed by creating session with expire_on_commit=False param.
>>> from sqlalchemy import inspect
>>> insp = inspect(my_object)
>>> insp.expired
True # then it will be refreshed...

In my case, I was saving a related entity as well, and this recipe helped me to refresh all instances within a session, leveraging the fact that Session is iterable:
map(session.refresh, iter(session)) # call refresh() on every instance
This is extremely ineffective, but works. Should be fine for unit-tests.
Final note: in Python3 map() is a generator and won't do anything. Use real loops of list comprehensions

Related

Drawbacks of executing code in an SQLAlchemy managed session and if so why?

I have seen different "patterns" in handling this case so I am wondering if one has any drawbacks comapred to the other.
So lets assume that we wish to create a new object of class MyClass and add it to the database. We can do the following:
class MyClass:
pass
def builder_method_for_myclass():
# A lot of code here..
return MyClass()
my_object=builder_method_for_myclass()
with db.managed_session() as s:
s.add(my_object)
which seems that only keeps the session open for adding the new object but I have also seen cases where the entire builder method is called and executed within the managed session like so:
class MyClass:
pass
def builder_method_for_myclass():
# A lot of code here..
return MyClass()
with db.managed_session() as s:
my_object=builder_method_for_myclass()
are there any downsides in either of these methods and if yes what are they? Cant find something specific about this in the documentation.
When you build objects depending on objects fetched from a session you have to be in a session. So a factory function can only execute outside a session for the simplest cases. Usually you have to pass the session around or make it available on a thread local.
For example in this case to build a product I need to fetch the product category from the database into the session. So my product factory function depends on the session instance. The new product is created and added to the same session that the category is also in. An implicit commit should also occur when the session ends, ie the context manager completes.
def build_product(session, category_name):
category = session.query(ProductCategory).where(
ProductCategory.name == category_name).first()
return Product(category=category)
with db.managed_session() as s:
my_product = build_product(s, "clothing")
s.add(my_product)

Instance is not bound to a Session

Just like many other people I run into the problem of an instance not being bound to a Session. I have read the SQLAlchemy docs and the top-10 questions here on SO. Unfortunatly I have not found an explanation or solution to my error.
My guess is that the commit() closes the session rendering the object unbound. Does this mean that In need to create two objects, one to use with SQL Alchemy and one to use with the rest of my code?
I create an object something like this:
class Mod(Base):
__tablename__ = 'mod'
insert_timestamp = Column(DateTime, default=datetime.datetime.now())
name = Column(String, nullable=False)
Then I add it to the database using this function and afterwards the object is usesless, I cannot do anything with it anymore, always getting the error that it is not bound to a session. I have tried to keep the session open, keep it closed, copy the object, open two sessions, return the object, return a copy of the object.
def add(self, dataobjects: list[Base]) -> bool:
s = self.sessionmaker()
try:
s.add_all(dataobjects)
except TypeError:
s.rollback()
raise
else:
s.commit()
return True
This is my session setup:
self.engine = create_engine(f"sqlite:///{self.config.database['file']}")
self.sessionmaker = sessionmaker(bind=self.engine)
Base.metadata.bind = self.engine
My last resort would be to create every object twice, once for SQL Alchemy and once so I can actually use the object in my code. This defeats the purpose of SQL Alchemy for me.
ORM entities are expired when committed. By default, if an entity attribute is accessed after expiry, the session emits a SELECT to get the current value from the database. In this case, the session only exists in the scope of the add method, so subsequent attribute access raises the "not bound to a session" error.
There are a few ways to approach this:
Pass expire _on_commit=False when creating the session. This will prevent automatic expiry so attribute values will remain accessible after leaving the add function, but they may be stale.
Create the session outside of the add function and pass it as an argument (or return it from add, though that's rather ugly). As long as the session is not garbage collected the entities remain bound to it.
Create a new session and do mod = session.merge(mod) for each entity to bind it to the new session.
Which option you choose depends on your application.

Instance is not bound to a Session after expunge_all called [duplicate]

I am trying to get an collection of objects out of a database and pass it to another process that is not connected to the database. My code looks like the one below but I keep getting:
sqlalchemy.exc.UnboundExecutionError: Instance <MyClass at 0x8db7fec> is not bound to a Session; attribute refresh operation cannot proceed
When I try to look at the elements of my list outside of the get_list() method.
def get_list (obj):
sesson = Session()
lst = session.query(MyClass).all()
session.close()
return lst
However, if I use this:
def get_list_bis (obj)
session = Session()
return session.query(MyClass).all()
I am able to use the elements but worry about the state of the session since it was not closed.
What am I missing here?
If you want a bunch of objects produced by querying a session to be usable outside the scope of the session, you need to expunge them for the session.
In your first function example, you will need to add a line:
session.expunge_all()
before
session.close()
More generally, let's say the session is not closed right away, like in the first example. Perhaps this is a session that is kept active during entire duration of a web request or something like that. In such cases, you don't want to do expunge_all. You will want to be more surgical:
for item in lst:
session.expunge(item)
This often happens due to objects being in expired state, objects get expired for example after committing, then when such expired objects are about to get used the ORM tries to refresh them, but this cannot be done when objects are detached from session (e.g. because that session was closed). This behavior can be managed by creating session with expire_on_commit=False param.
>>> from sqlalchemy import inspect
>>> insp = inspect(my_object)
>>> insp.expired
True # then it will be refreshed...
In my case, I was saving a related entity as well, and this recipe helped me to refresh all instances within a session, leveraging the fact that Session is iterable:
map(session.refresh, iter(session)) # call refresh() on every instance
This is extremely ineffective, but works. Should be fine for unit-tests.
Final note: in Python3 map() is a generator and won't do anything. Use real loops of list comprehensions

django session check for existance

I am trying to check if the session does have anything in it. What I did is:
if request.session:
# do something
but it is not working. Is there any way of knowing whether the session contains something at that moment?
If I do request.session['some_name'], it works, but in some cases, I just need to know if the session is empty or not.
Asking with some specific names is not always a wanted thing.
Eg. if there is no session, it returns an error, since some_name doesn't exist.
request.session is an instance of SessionBase object which behaves like dictionary but it it is not a dictionary. This object has a "private" field ( actually it's a property ) called _session which is a dictionary which holds all data.
The reason for that is that Django does not load session until you call request.session[key]. It is lazily instantiated.
So you can try doing that:
if request.session._session:
# do something
or you can do it by looking at keys like this:
if request.session.keys():
# do something
Note how .keys() works:
django/contrib/sessions/backends/base.py
class SessionBase(object):
# some code
def keys(self):
return self._session.keys()
# some code
I always recommend reading the source code directly.
Nowadays there's a convenience method on the session object
request.session.is_empty()

Parent instance is not bound to a Session; lazy load operation of attribute ’account’ cannot proceed

While trying to do the following operation:
for line in blines:
line.account = get_customer(line.AccountCode)
I am getting an error while trying to assign a value to line.account:
DetachedInstanceError: Parent instance <SunLedgerA at 0x16eda4d0> is not bound to a Session; lazy load operation of attribute 'account' cannot proceed
Am I doing something wrong??
"detached" means you're dealing with an ORM object that is not associated with a Session. The Session is the gateway to the relational database, so anytime you refer to attributes on the mapped object, the ORM will sometimes need to go back to the database to get the current value of that attribute. In general, you should only work with "attached" objects - "detached" is a temporary state used for caching and for moving objects between sessions.
See Quickie Intro to Object States, then probably read the rest of that document too ;).
I had the same problem with Celery. Adding lazy='subquery' to relationship solved my problem.
I encountered this type of DetachedInstanceError when I prematurely close the query session (that is, having code to deal with those SQLAlchemy model objects AFTER the session is closed). So that's one clue to double check no session closure until you absolutely don't need interact with model objects, I.E. some Lazy Loaded model attributes etc.
I had the same problem when unittesting.
The solution was to call everything within the "with" context:
with self.app.test_client() as c:
res = c.post('my_url/test', data=XYZ, content_type='application/json')
Then it worked.
Adding the lazy attribute didn't work for me.
To access the attribute connected to other table, you should call it within session.
#contextmanager
def get_db_session(engine):
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = SessionLocal()
try:
yield db
except Exception:
db.rollback()
raise
finally:
db.close()
with get_db_session(engine) as sess:
data = sess.query(Groups).all()
# `group_users` is connected to other table
print([x.group_users for x in data]) # sucess
print([x.group_users for x in data]) # fail

Categories