Source code for summer.sf

# -*- coding: utf-8 -*-
# Time-stamp: < sf.py (2016-02-14 02:23) >

# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Module ``sf`` defines :py:class:`SessionFactory` class which is central
point for your ORM mapping and *SQL database* access.

"""

import logging
import threading

from sqlalchemy import (create_engine, MetaData)
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session

from .ex import SummerConfigurationException

logger = logging.getLogger(__name__)


[docs]class SessionFactory(object): """Thread safe *SQLAlchemy* session provider."""
[docs] class Local(threading.local): """Thread local session wrapper.""" def __init__(self): threading.local.__init__(self) self.sqlalchemy_session = None self.active = False # True if transaction is active def __del__(self): self.close() def close(self): if self.sqlalchemy_session: self.sqlalchemy_session.close() self.sqlalchemy_session = None self.active = False # FIXME martin.slouf -- logger may not be accessible as module may be destroyed before object # logger.debug("thread-local db session closed") def begin(self): if self.sqlalchemy_session and self.active: self.sqlalchemy_session.begin() self.active = True def commit(self): if self.sqlalchemy_session: self.sqlalchemy_session.commit() self.active = True def rollback(self): if self.sqlalchemy_session: self.sqlalchemy_session.rollback() self.active = True
[docs] def __init__(self, uri: str, echo: bool, autoflush: bool, autocommit: bool): """Creates :py:class:`SessionFactory` instance. Args: uri (str): *SQLAlchemy* connection string (including username and password) echo (bool): print out SQL commands autoflush (bool): whether autoflush is enabled (if unsure, set to ``True``) autocommit (bool): whether autocommit is enabled (if unsure set to ``False``) """ self.uri = uri self.echo = echo self.autoflush = autoflush self.autocommit = autocommit self.session = SessionFactory.Local() self.rlock = threading.RLock() # NOTE martin.slouf -- needs to set via 'set_table_definitions' self.table_definitions = None # NOTE martin.slouf -- needs to set via 'set_class_mappings' self.class_mappings = None self.engine = create_engine(uri, echo=echo) self.metadata = MetaData(bind=self.engine) self.sessionmaker = sessionmaker(bind=self.engine, autoflush=autoflush, autocommit=autocommit)
[docs] def set_table_definitions(self, table_definitions): """Sets table definitons. See :py:meth:`summer.context.Context.orm_init` method. """ self.table_definitions = table_definitions self.table_definitions.define_tables(self) logger.info("table definitions registered: %s", self.metadata.tables)
[docs] def set_class_mappings(self, class_mappings): """Sets class mappings. See :py:meth:`summer.context.Context.orm_init` method. """ if self.table_definitions == None: msg = "unable to register mappings -- set table definitions first" raise SummerConfigurationException(msg) self.class_mappings = class_mappings self.class_mappings.create_mappings(self) logger.info("class mappings registered")
[docs] def create_schema(self): """Create database schema using *SQLAlchemy*. Call once :py:attr:`table_definitions` are set. """ if self.table_definitions == None: msg = "unable to create schema -- set table definitions first" raise SummerConfigurationException(msg) # delegate call to ORM layer self.metadata.drop_all() self.metadata.create_all()
[docs] def get_session(self): """Get current thread-local *SQLAlchemy session* wrapper (creating one, if non-exististent). Returns: SessionFactory.Local: existing or just created *SQLAlchemy session* wrapper """ sqlalchemy_session = self.session.sqlalchemy_session if sqlalchemy_session: logger.debug("accessing session = %s", self.session) else: sqlalchemy_session = self.sessionmaker() logger.debug("new thread local session created, session = %s", self.session) self.session.sqlalchemy_session = sqlalchemy_session return self.session
[docs] def get_sqlalchemy_session(self) -> Session: """Get current *SQLAlchemy* session. See :py:meth:`get_session` method. Returns: Session: existing of just created *SQLAlchemy* session. """ return self.get_session().sqlalchemy_session
[docs]class AbstractTableDefinitions(object): """ Container for *SQLAlchemy* table definitions. Registers itself at session factory. A callback class -- use to provide table definitions to ORM. See :py:meth:`summer.context.Context.orm_init` method. """
[docs] def define_tables(self, session_factory: SessionFactory): """Override in subclasses to define database tables.""" pass
[docs]class AbstractClassMappings(object): """Container for *SQLAlchemy* mappings. Registers itself at session factory. A callback class -- use to provide class mappings to ORM. See :py:meth:`summer.context.Context.orm_init` method. """
[docs] def create_mappings(self, session_factory: SessionFactory): """Override in subclasses to define mappings (tables to ORM classes -- entities). """ pass