Source code for database.sessions

"""
Contains classes related to session management with the DB.
"""
from sqlalchemy.orm import Session
import logging
__author__ = 'Michal Kononenko'

log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)


[docs]class ContextManagedSession(Session): """ An extension to :class:`sqlalchemy.orm.Session` that allows the session to be run using a ``with`` statement, committing all changes on an error-free exit from the context manager. This class also allows deep copies of itself to be injected dynamically into function arguments, when employed as a decorator. .. note:: In order to make the best use of :class:`ContextManagedSession`, it is recommended that this class is first instantiated in a module-level scope, with its ``bind`` (i.e. the SQLAlchemy `engine`_ or connection to which the session is bound), declared in as global a scope as possible. In particular, engines should be declared as globally as possible in order to maximize the efficiency of SQLAlchemy's `connection pooling`_ features. The module-level instantiation is then used as a master from which copies are created as needed. ** Example ** The following example shows how to use the session as both a context manager and a decorator .. code-block:: python import engine # import the DB engine from somewhere else if needed session = ContextManagedSession(bind=engine) @session() def do_something(session): session.query(something) def do_something_context_managed(): with session() as new_session: new_session.query(something) .. _engine: http://docs.sqlalchemy.org/en/latest/core/engines.html .. _connection pooling: http://goo.gl/tIJ33L """
[docs] def copy(self): """ Returns a new :class:`ContextManagedSession` with the same namespace as ``self`` """ session = self.__class__() session.__dict__ = self.__dict__ return session
[docs] def __call__(self, f=None): """ Returns itself, provided that the argument ``f`` is not a `callable`_ function. (i.e., it does not have a ``__call__`` method defined) :param f: The function to be decorated. If no function is provided, then f is ``None``. :type f: function or None :return: A deep copy of this session, or a decorator function that can then inject arguments dynamically into the function to be decorated. :rtype: :class:`ContextManagedSession` or a function .. _callable: http://goo.gl/qCr2Uk """ if hasattr(f, '__call__'): return self._decorator(f) else: return self
[docs] def _decorator(self, f): """ This method decorates a function f with the session to be run. This is done by invoking the :class:`ContextManagedSession`'s context manager to open and clean up a new session, and running the decorated function in this context. The session is then appended to the decorated function's argument list, and the function is then run. For reconciling method names and docstring for documentation purposes, and to avoid namespace collisions in the server's routing table, the ``__name__`` and ``__doc__`` properties of the decorated function are assigned to the decorator. :param function f: The function to decorate :return: A function that decorates the function to be decorated :rtype: function """ def _wrapped_function(*args, **kwargs): """ Wraps the function to be decorated. This function opens the context manager and runs ``f`` provided from :meth:`ContextManagedSession._decorator`'s scope, with the arguments and keyword arguments passed into f. :param args: The arguments with which the decorated function ``f`` was called. :param kwargs: The keyword arguments with which the decorated ``f`` was called. :return: The return value of the function ``f``, when run with the session from the new context appended to the argument list. """ with self as session: new_args = args + (session,) response = f(*new_args, **kwargs) return response _wrapped_function.__name__ = f.__name__ _wrapped_function.__doc__ = f.__doc__ return _wrapped_function
[docs] def __enter__(self): """ Magic method that opens a new context for the session, returning a deep copy of the session to use in the new context. :return: A deep copy of ``self`` :rtype: :class:`ContextManagedSession` """ return self.copy()
[docs] def __exit__(self, exc_type, exc_val, _): """ Magic method that is responsible for exiting the context created in :meth:`ContextManagedSession.__enter__`; running the required clean-up logic in order to flush changes made in the session to the database. If there are no exceptions thrown in the :class:`ContextManagedSession`'s context, it is assumed that the code inside the context completed successfully. In this case, the changes made to the database in this session will be committed. If an exception was thrown in this context, then the database will be rolled back to the state before any logic inside the context manager will be executed. The arguments taken into this method are the standard arguments taken into an ``__exit__`` `method`_. The last argument ``_`` is used as a placeholder for the stack trace of the exception thrown in the context. This parameter is currently not used in this method, as re-throwing an exception prepends the stack trace of the exception prior to re-throw into the exception. Python is awesome this way :)! .. _method: http://goo.gl/ZHXkZ4 :param type exc_type: The class of exception that was thrown in the context. This is ``None`` if no exception was thrown :param exc_type exc_val: The particular instance of the exception that was thrown during execution in the context. After the session is rolled back, this exception is re-thrown. """ if exc_type is None: try: self.commit() except BaseException as exc: log.error('Session commit raised error %s', exc) self.rollback() raise exc else: self.rollback() raise exc_val
[docs] def __repr__(self): """ Returns a string representation of an instance of :class:`ContextManagedSession` useful for debugging. :return: A string representing useful data about an instance of this class :rtype: str """ return '%s(bind=%s, expire_on_commit=%s)' % \ (self.__class__.__name__, self.bind, self.expire_on_commit)