Wyko ter Haar
Wyko ter Haar

Reputation: 158

How to use a SQLAlchemy scoped_session in a context manager?

Per the docs, we should use the following pattern with a sessionmaker object:

Session = sessionmaker(engine)
with Session.begin() as session:
    session.add(some_object)

In a multithreaded environment, we are also supposed to use a single scoped_session and share it. So in my __init__.py I create create one and import it everywhere else in my program:

engine = create_engine(config.SQLALCHEMY_DATABASE_URI)
Session = scoped_session(sessionmaker(bind=engine))

The question is, how am I supposed to combine these two approaches? This seems to be the suggested way, but it errors out:

from myapp import Session
with Session.begin() as session:
    query_result = session.query(MyModel).all()

----
Exception has occurred: AttributeError
'SessionTransaction' object has no attribute 'query'

I tried the following and it works, but it seems like it doesn't follow the docs, and I'm afraid it breaks something not obvious. Can anyone confirm if this is correct?

from myapp import Session
with Session() as session, session.begin():
    query_result = session.query(MyModel).all()

I've been looking around at other replies and seeing very little that addresses the specific question.

Upvotes: 1

Views: 5128

Answers (1)

ljmc
ljmc

Reputation: 5315

From the Session.begin() docs:

The Session object features autobegin behavior, so that normally it is not necessary to call the Session.begin() method explicitly. However, it may be used in order to control the scope of when the transactional state is begun.

You can use Session.begin() (new in 1.4) to obtain a SessionTransaction instance usable as a context manager which will autocommit on successful exit.

Calling the scoped_session returns a SessionTransaction right away, as per your error, so you do not need to begin it again.

All in all, you can definitely do the stacked context manager, but its unnecessary, so you might as well stick to using the original flow:

Session = scoped_session(...)

with Session() as session:  # NB. session is a thread local SessionTransaction
    ...
    session.commit()

or the proxied Session

Session = scoped_session(...)

@on_request_end
def remove_session(req):
    Session.remove()

@route("/xyz", ...)
def handle_xyz():
    instance = Class(...)
    Session.add(instance)
    Session.commit()

Upvotes: 8

Related Questions