Package turbogears :: Module database
[hide private]

Source Code for Module turbogears.database

  1  """Convenient access to an SQLObject or SQLAlchemy managed database.""" 
  2   
  3  import sys 
  4  import time 
  5  import logging 
  6   
  7  import cherrypy 
  8  from cherrypy.filters.basefilter import BaseFilter 
  9   
 10  try: 
 11      import sqlalchemy 
 12      from sqlalchemy.orm import create_session as orm_create_session 
 13  except ImportError: 
 14      sqlalchemy = None 
 15   
 16  try: 
 17      import sqlobject 
 18      from sqlobject.dbconnection import ConnectionHub, Transaction, TheURIOpener 
 19      from sqlobject.util.threadinglocal import local as threading_local 
 20  except ImportError: 
 21      sqlobject = None 
 22   
 23  import dispatch 
 24  from turbogears import config 
 25  from turbogears.util import remove_keys 
 26  from turbogears.genericfunctions import MultiorderGenericFunction 
 27   
 28  log = logging.getLogger("turbogears.database") 
 29   
 30  _engine = None 
 31   
 32  # Provide support for SQLAlchemy 
 33  if sqlalchemy: 
34 - def get_engine():
35 """Retrieve the engine based on the current configuration.""" 36 global _engine 37 if not _engine: 38 alch_args = dict() 39 for k, v in config.config.configMap["global"].items(): 40 if "sqlalchemy" in k: 41 alch_args[k.split(".")[-1]] = v 42 dburi = alch_args.pop('dburi') 43 if not dburi: 44 raise KeyError("No sqlalchemy database config found!") 45 _engine = sqlalchemy.create_engine(dburi, **alch_args) 46 if not metadata.is_bound(): 47 metadata.bind = _engine 48 return _engine
49
50 - def create_session():
51 """Create a session that uses the engine from thread-local metadata.""" 52 if not metadata.is_bound(): 53 get_engine() 54 return orm_create_session()
55 56 metadata = sqlalchemy.MetaData() 57 try: 58 from sqlalchemy.orm import scoped_session 59 session = scoped_session(create_session) 60 mapper = session.mapper # use session-aware mapper 61 except ImportError: # SQLAlchemy < 0.4 62 from sqlalchemy.ext.sessioncontext import SessionContext
63 - class Objectstore(object):
64 - def __init__(self):
65 self.context = SessionContext(create_session)
66 - def __getattr__(self, name):
67 return getattr(self.context.registry(), name)
68 session = property(lambda s: s.context.registry())
69 session = Objectstore() 70 context = session.context 71 Query = sqlalchemy.Query 72 from sqlalchemy.orm import mapper as orm_mapper
73 - def mapper(cls, *args, **kwargs):
74 validate = kwargs.pop('validate', False) 75 if not hasattr(getattr(cls, '__init__'), 'im_func'): 76 def __init__(self, **kwargs): 77 for key, value in kwargs.items(): 78 if validate and key not in self.mapper.props: 79 raise KeyError( 80 "Property does not exist: '%s'" % key) 81 setattr(self, key, value)
82 cls.__init__ = __init__ 83 m = orm_mapper(cls, extension=context.mapper_extension, 84 *args, **kwargs) 85 class query_property(object): 86 def __get__(self, instance, cls): 87 return Query(cls, session=context.current) 88 cls.query = query_property() 89 return m 90 91 try: 92 from sqlalchemy.ext import activemapper 93 activemapper.metadata, activemapper.objectstore = metadata, session 94 except ImportError: 95 pass 96 try: 97 import elixir 98 elixir.metadata, elixir.session = metadata, session 99 except ImportError: 100 pass 101 102 else:
103 - def get_engine():
104 pass
105
106 - def create_session():
107 pass
108 109 metadata = session = mapper = None 110 111 bind_meta_data = bind_metadata = get_engine # alias names 112 113 try: 114 set 115 except NameError: # Python 2.3 116 from sets import Set as set 117 118 hub_registry = set() 119 120 _hubs = dict() # stores the AutoConnectHubs used for each connection URI 121 122 # Provide support for SQLObject 123 if sqlobject:
124 - def _mysql_timestamp_converter(raw):
125 """Convert a MySQL TIMESTAMP to a floating point number representing 126 the seconds since the Un*x Epoch. It uses custom code the input seems 127 to be the new (MySQL 4.1+) timestamp format, otherwise code from the 128 MySQLdb module is used.""" 129 if raw[4] == '-': 130 return time.mktime(time.strptime(raw, '%Y-%m-%d %H:%M:%S')) 131 else: 132 import MySQLdb.converters 133 return MySQLdb.converters.mysql_timestamp_converter(raw)
134 135
136 - class AutoConnectHub(ConnectionHub):
137 """Connects to the database once per thread. The AutoConnectHub also 138 provides convenient methods for managing transactions.""" 139 uri = None 140 params = {} 141
142 - def __init__(self, uri=None, supports_transactions=True):
143 if not uri: 144 uri = config.get("sqlobject.dburi") 145 self.uri = uri 146 self.supports_transactions = supports_transactions 147 hub_registry.add(self) 148 ConnectionHub.__init__(self)
149
150 - def _is_interesting_version(self):
151 """Return True only if version of MySQLdb <= 1.0.""" 152 import MySQLdb 153 module_version = MySQLdb.version_info[0:2] 154 major = module_version[0] 155 minor = module_version[1] 156 # we can't use Decimal here because it is only available for Python 2.4 157 return (major < 1 or (major == 1 and minor < 2))
158
159 - def _enable_timestamp_workaround(self, connection):
160 """Enable a workaround for an incompatible timestamp format change 161 in MySQL 4.1 when using an old version of MySQLdb. See trac ticket 162 #1235 - http://trac.turbogears.org/ticket/1235 for details.""" 163 # precondition: connection is a MySQLConnection 164 import MySQLdb 165 import MySQLdb.converters 166 if self._is_interesting_version(): 167 conversions = MySQLdb.converters.conversions.copy() 168 conversions[MySQLdb.constants.FIELD_TYPE.TIMESTAMP] = \ 169 _mysql_timestamp_converter 170 # There is no method to use custom keywords when using 171 # "connectionForURI" in sqlobject so we have to insert the 172 # conversions afterwards. 173 connection.kw["conv"] = conversions
174
175 - def getConnection(self):
176 try: 177 conn = self.threadingLocal.connection 178 return self.begin(conn) 179 except AttributeError: 180 if self.uri: 181 conn = sqlobject.connectionForURI(self.uri) 182 # the following line effectively turns off the DBAPI connection 183 # cache. We're already holding on to a connection per thread, 184 # and the cache causes problems with sqlite. 185 if self.uri.startswith("sqlite"): 186 TheURIOpener.cachedURIs = {} 187 elif self.uri.startswith("mysql") and \ 188 config.get("turbogears.enable_mysql41_timestamp_workaround", False): 189 self._enable_timestamp_workaround(conn) 190 self.threadingLocal.connection = conn 191 return self.begin(conn) 192 raise AttributeError( 193 "No connection has been defined for this thread " 194 "or process")
195
196 - def reset(self):
197 """Used for testing purposes. This drops all of the connections 198 that are being held.""" 199 self.threadingLocal = threading_local()
200
201 - def begin(self, conn=None):
202 """Start a transaction.""" 203 if not self.supports_transactions: 204 return conn 205 if not conn: 206 conn = self.getConnection() 207 if isinstance(conn, Transaction): 208 if conn._obsolete: 209 conn.begin() 210 return conn 211 self.threadingLocal.old_conn = conn 212 trans = conn.transaction() 213 self.threadingLocal.connection = trans 214 return trans
215
216 - def commit(self):
217 """Commit the current transaction.""" 218 if not self.supports_transactions: 219 return 220 try: 221 conn = self.threadingLocal.connection 222 except AttributeError: 223 return 224 if isinstance(conn, Transaction): 225 self.threadingLocal.connection.commit()
226
227 - def rollback<