Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/pylons/pylons/database.py
0001"""Depreacted: pylons.database will be removed from a future version of
0002Pylons. SQLAlchemy 0.3.x users are recommended to migrate to SAContext
0003(http://cheeseshop.python.org/pypi/SAContext) for similar functionality.
0004
0005Provides convenient access to SQLObject-managed and/or SQLAlchemy-managed
0006databases.
0007
0008This module enables easy use of an SQLObject database by providing an 
0009auto-connect hub that will utilize the db uri string given in the Paste conf
0010file called ``sqlobject.dburi``.
0011
0012A SQLAlchemy ``SessionContext`` is also available: it provides both thread and
0013process safe ``Session`` objects via the ``session_context.current`` property.
0014"""
0015import logging
0016import thread
0017import warnings
0018
0019from paste.deploy.converters import asbool
0020
0021import pylons
0022
0023__all__ = ["AutoConnectHub", "PackageHub"]
0024
0025log = logging.getLogger(__name__)
0026
0027warnings.warn(pylons.legacy.pylons_database_warning, DeprecationWarning, 2)
0028
0029try:
0030    import sqlalchemy
0031    from sqlalchemy.ext import sessioncontext
0032
0033    BOOL_OPTIONS = set([
0034            "convert_unicode",
0035            "echo",
0036            "echo_pool",
0037            "threaded",
0038            "use_ansi",
0039            "use_oids",
0040            ])
0041
0042    INT_OPTIONS = set([
0043            "max_overflow",
0044            "pool_size",
0045            "pool_recycle",
0046            "pool_timeout",
0047            ])
0048
0049    def app_scope():
0050        """Return the id keying the current database session's scope.
0051
0052        The session is particular to the current Pylons application -- this
0053        returns an id generated from the current thread and the current Pylons
0054        application's Globals object at pylons.g (if one is registered).
0055        """
0056        try:
0057            app_scope_id = str(id(pylons.config._current_obj()))
0058        except TypeError:
0059            app_scope_id = ''
0060        log.debug("Returning %s as the database session scope id",
0061                  app_scope_id)
0062        return '%s|%i' % (app_scope_id, thread.get_ident())
0063
0064    def create_engine(uri=None, echo=None, **kwargs):
0065        """Return a SQLAlchemy db engine. Uses the configuration values from
0066        ``get_engine_conf``.
0067
0068        Engines are cached in the ``get_engines`` dict.
0069        """
0070        conf = get_engine_conf()
0071        conf.update(kwargs)
0072
0073        # replace 'dburi' with 'uri' for consistency
0074        if 'dburi' in conf:
0075            if not 'uri' in conf:
0076                conf['uri'] = conf['dburi']
0077            del conf['dburi']
0078
0079        # override config with passed-in values
0080        conf['uri'] = uri or conf.get('uri')
0081        conf['echo'] = asbool(echo) or conf.get('echo')
0082
0083        uri = conf.pop('uri')
0084        assert uri
0085
0086        # call create_engine or fetch engine from cache
0087
0088        ## use a sorted list of tuples since order isn't guaranteed
0089        ## in the dict
0090        conf_key = str(sorted(conf.items(), key=lambda x: x[0]))
0091
0092        engine_key = '%s|%s' % (uri, conf_key)
0093        db_engines = pylons.config['pylons.db_engines']
0094        if engine_key in db_engines:
0095            engine = db_engines[engine_key]
0096        else:
0097            engine = db_engines[engine_key] =                   sqlalchemy.create_engine(uri, **conf)
0099
0100        log.debug("Created engine using uri: %s with engine arguments %s", uri, conf)
0101        return engine
0102
0103    def get_engine_conf():
0104        """Returns a dict of SQLAlchemy engine configuration values
0105        from the Pylons config file values ``sqlalchemy.*``"""
0106        result = {}
0107        for k,v in pylons.config.iteritems():
0108            if not k.startswith('sqlalchemy.'):
0109                continue
0110            k = k[11:]
0111            if k in BOOL_OPTIONS:
0112                result[k] = asbool(v)
0113            elif k in INT_OPTIONS:
0114                try:
0115                    result[k] = int(v)
0116                except ValueError:
0117                    reason = 'config sqlalchemy.%s is not an integer: %s'
0118                    raise ValueError(reason % (k,v))
0119            else:
0120                result[k] = v
0121        return result
0122
0123    def make_session(uri=None, echo=None, session_kwargs=None, **kwargs):
0124        """Returns a SQLAlchemy session for the specified database uri from
0125        the the engine cache (returned from ``get_engines``)``. Uses the
0126        configuration values from ``get_engine_conf`` for uri and echo when
0127        None are specified.
0128
0129        ``session_kwargs`` are passed to SQLAlchemy's ``create_session``
0130        function as keyword arguments.
0131        
0132        If the uri's engine does not exist, it will be created and added to
0133        the engine cache.
0134        """
0135        if session_kwargs is None:
0136            session_kwargs = {}
0137        engine = create_engine(uri, echo=echo, **kwargs)
0138        log.debug("Created engine for session context")
0139        return sqlalchemy.create_session(bind_to=engine, **session_kwargs)
0140
0141    session_context = sessioncontext.SessionContext(make_session,
0142                                                    scopefunc=app_scope)
0143
0144    __all__.extend(['app_scope', 'create_engine', 'get_engine_conf',
0145                    'make_session', 'session_context'])
0146
0147except ImportError:
0148    pass
0149
0150
0151# Provide support for sqlobject
0152try:
0153    import sqlobject
0154    from sqlobject.dbconnection import ConnectionHub, Transaction, TheURIOpener
0155except:
0156    ConnectionHub = object
0157
0158
0159class AutoConnectHub(ConnectionHub):
0160    """Connects to the database once per thread.
0161    
0162    The AutoConnectHub also provides convenient methods for managing
0163    transactions.
0164    """
0165    uri = None
0166    params = {}
0167
0168    def __init__(self, uri=None, pool_connections=True):
0169        if not uri:
0170            uri = pylons.config.get('sqlobject.dburi')
0171        self.uri = uri
0172        self.pool_connections = pool_connections
0173        ConnectionHub.__init__(self)
0174
0175    def getConnection(self):
0176        try:
0177            conn = self.threadingLocal.connection
0178            return conn
0179        except AttributeError:
0180            if self.uri:
0181                conn = sqlobject.connectionForURI(self.uri)
0182                # the following line effectively turns off the DBAPI connection
0183                # cache. We're already holding on to a connection per thread,
0184                # and the cache causes problems with sqlite.
0185                if self.uri.startswith("sqlite"):
0186                    TheURIOpener.cachedURIs = {}
0187                self.threadingLocal.connection = conn
0188                if not self.pool_connections:
0189                    # This disables pooling
0190                    conn._pool = None
0191                return conn
0192            try:
0193                return self.processConnection
0194            except AttributeError:
0195                raise AttributeError(
0196                    "No connection has been defined for this thread "
0197                    "or process")
0198
0199    def begin(self):
0200        """Starts a transaction."""
0201        conn = self.getConnection()
0202        if isinstance(conn, Transaction):
0203            if conn._obsolete:
0204                conn.begin()
0205            return
0206        self.threadingLocal.old_conn = conn
0207        self.threadingLocal.connection = conn.transaction()
0208
0209    def commit(self):
0210        """Commits the current transaction."""
0211        conn = self.threadingLocal.connection
0212        if isinstance(conn, Transaction):
0213            self.threadingLocal.connection.commit()
0214
0215    def rollback(self):
0216        """Rolls back the current transaction."""
0217        conn = self.threadingLocal.connection
0218        if isinstance(conn, Transaction) and not conn._obsolete:
0219            self.threadingLocal.connection.rollback()
0220
0221    def end(self):
0222        """Ends the transaction, returning to a standard connection."""
0223        conn = self.threadingLocal.connection
0224        if not isinstance(conn, Transaction):
0225            return
0226        if not conn._obsolete:
0227            conn.rollback()
0228        self.threadingLocal.connection = self.threadingLocal.old_conn
0229        del self.threadingLocal.old_conn
0230        self.threadingLocal.connection.cache.clear()
0231
0232
0233# This dictionary stores the AutoConnectHubs used for each
0234# connection URI
0235_hubs = dict()
0236
0237
0238class UnconfiguredConnectionError(KeyError):
0239    """
0240    Raised when no configuration is available to set up a connection.
0241    """
0242
0243
0244class PackageHub(object):
0245    """Transparently proxies to an AutoConnectHub for the URI
0246    that is appropriate for this package. A package URI is
0247    configured via "packagename.dburi" in the Paste ini file
0248    settings. If there is no package DB URI configured, the
0249    default (provided by "sqlobject.dburi") is used.
0250    
0251    The hub is not instantiated until an attempt is made to
0252    use the database.
0253
0254    If pool_connections=False, then a new database connection
0255    will be opened for every request.  This will avoid
0256    problems with database connections that periodically die.
0257    """
0258    def __init__(self, packagename, dburi=None, pool_connections=True):
0259        self.packagename = packagename
0260        self.hub = None
0261        self.dburi = dburi
0262        self.pool_connections = pool_connections
0263
0264    def __get__(self, obj, type):
0265        if not self.hub:
0266            try:
0267                self.set_hub()
0268            except UnconfiguredConnectionError, e:
0269                raise AttributeError(str(e))
0270        return self.hub.__get__(obj, type)
0271
0272    def __set__(self, obj, type):
0273        if not self.hub:
0274            self.set_hub()
0275        return self.hub.__set__(obj, type)
0276
0277    def __getattr__(self, name):
0278        if not self.hub:
0279            self.set_hub()
0280        return getattr(self.hub, name)
0281
0282    def set_hub(self):
0283        dburi = self.dburi
0284        if not dburi:
0285            try:
0286                dburi = pylons.config.get("%s.dburi" %                                                      self.packagename)
0288            except TypeError, e:
0289                # No configuration is registered
0290                raise UnconfiguredConnectionError(str(e))
0291        if not dburi:
0292            dburi = pylons.config.get("sqlobject.dburi")
0293        if not dburi:
0294            raise UnconfiguredConnectionError(
0295                "No database configuration found!")
0296        hub = _hubs.get(dburi)
0297        if not hub:
0298            hub = AutoConnectHub(
0299                dburi, pool_connections=self.pool_connections)
0300            _hubs[dburi] = hub
0301        self.hub = hub

Top