Skip to content

Instantly share code, notes, and snippets.

@obeattie
Created October 14, 2009 12:51
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save obeattie/210032 to your computer and use it in GitHub Desktop.
Save obeattie/210032 to your computer and use it in GitHub Desktop.
Exposes SQLAlchemy's sessions and transactions as context managers (so they will be managed automatically inside blocks), and also provides a transaction decorator, which wraps an entire function in a transaction
"""Utilities for managing database sessions."""
from __future__ import with_statement
import contextlib
import functools
@contextlib.contextmanager
def temp_session(session_cls, **kwargs):
"""Quick and dirty context manager that provides a temporary Session object
to the nested block. The session is always closed at the end of the block.
This is useful if only SELECTs and the like are being done; anything involving
INSERTs, UPDATEs etc should use transactional_session."""
session = session_cls(**kwargs)
yield session
@contextlib.contextmanager
def transactional_session(session_cls, nested=True, **kwargs):
"""Context manager which provides transaction management for the nested
block. A transaction is started when the block is entered, and then either
committed if the block exits without incident, or rolled back if an error
is raised.
Nested (SAVEPOINT) transactions are enabled by default, unless nested=False is
passed, meaning that this context manager can be nested within another and the
transactions treated as independent units-of-work from the perspective of the nested
blocks. If the error is handled further up the chain, the outer transactions will
still be committed, while the inner ones will be rolled-back independently."""
session = session_cls(**kwargs)
session.begin(nested=nested)
try:
yield session
except:
# Roll back if the nested block raised an error
session.rollback()
raise
else:
# Commit if it didn't (so flow ran off the end of the try block)
session.commit()
def in_transaction(**session_kwargs):
"""Decorator which wraps the decorated function in a transactional session. If the
function completes successfully, the transaction is committed. If not, the transaction
is rolled back."""
def outer_wrapper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
with transactional_session(**session_kwargs):
return func(*args, **kwargs)
return wrapper
return outer_wrapper
@ubershmekel
Copy link

This relies on the ref-counting nature of CPython instead of explicitly closing the sessions?

@obeattie
Copy link
Author

How do you mean? The sessions are explicitly closed… lines 35 and 39

@ubershmekel
Copy link

I believe you should also call .close() after commit at 39 and after the yield at 15.

@obeattie
Copy link
Author

Yes, I think you're right actually. Looking at our code (which is a web app), there is a Session.close() that is performed at the end of each request (ie. on the class, not its instances) to clean this stuff up. I don't see any reason it isn't here, though… unless this would affect nested use? Been a long time since I wrote this stuff, so a little hazy on the details now.

@ubershmekel
Copy link

Actually I believe the sessions are closed as they should be right now if you're using CPython because of the reference counting. Once no one is using session it is cleaned up and closed. The problem might arise if CPython changes garbage collector or if you use this in PyPy or Jython...

@ajs
Copy link

ajs commented Jan 11, 2013

ubershmekel, the sessions will be cleaned up according to the whims of the GC... you really should call session.close() directly (though I think obeattie is correct that the class-level close will do).

@schlamar
Copy link

ajs is right ("explicit is better than implicit")

@sivy
Copy link

sivy commented Aug 14, 2013

I can't seem to make sense of the @in_transaction decorator...

Using transactional_session, I can do:

with transactional_session(session_maker) as session:
    thing = session.query(cls).filter(...)
    session.add(thing)

And the object will be added to the session and the session will be committed on exit from the context manager.

How do objects get added to the session when using @in_transaction, as it does not use as <var>, making the created session seemingly inaccessible? What am I missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment