""" DBPool.py Implements a pool of cached connections to a database. This should result in a speedup for persistent apps. The pool of connections is threadsafe regardless of whether the DB API module question in general has a threadsafety of 1 or 2. For more information on the DB API, see: http://www.python.org/topics/database/DatabaseAPI-2.0.html The idea behind DBPool is that it's completely seamless, so once you have established your connection, use it just as you would any other DB-API compliant module. For example: dbPool = DBPool(MySQLdb, 5, host=xxx, user=xxx, ...) db = dbPool.getConnection() Now use "db" exactly as if it were a MySQLdb connection. It's really just a proxy class. db.close() will return the connection to the pool, not actually close it. This is so your existing code works nicely. FUTURE * If in the presence of WebKit, register ourselves as a Can. CREDIT * Contributed by Dan Green * thread safety bug found by Tom Schwaller * Fixes by Geoff Talvola (thread safety in _threadsafe_getConnection()). * Clean up by Chuck Esterbrook. * Fix unthreadsafe functions which were leaking, Jay Love * Eli Green's webware-discuss comments were lifted for additional docs. """ import threading class DBPoolError(Exception): pass class UnsupportedError(DBPoolError): pass class PooledConnection: """ A wrapper for database connections to help with DBPool. You don't normally deal with this class directly, but use DBPool to get new connections. """ def __init__(self, pool, con): self._con = con self._pool = pool def close(self): if self._con is not None: self._pool.returnConnection(self) self._con = None def __getattr__(self, name): return getattr(self._con, name) def __del__(self): self.close() class DBPool: def __init__(self, dbModule, maxConnections, *args, **kwargs): if dbModule.threadsafety==0: raise UnsupportedError, "Database module does not support any level of threading." elif dbModule.threadsafety==1: from Queue import Queue self._queue = Queue(maxConnections) self.addConnection = self._unthreadsafe_addConnection self.getConnection = self._unthreadsafe_getConnection self.returnConnection = self._unthreadsafe_returnConnection elif dbModule.threadsafety>=2: self._lock = threading.Lock() self._nextCon = 0 self._connections = [] self.addConnection = self._threadsafe_addConnection self.getConnection = self._threadsafe_getConnection self.returnConnection = self._threadsafe_returnConnection # @@ 2000-12-04 ce: Should we really make all the connections now? # Couldn't we do this on demand? for i in range(maxConnections): con = apply(dbModule.connect, args, kwargs) self.addConnection(con) # threadsafe/unthreadsafe refers to the database _module_, not THIS class.. # this class is definitely threadsafe (um. that is, I hope so - Dan) def _threadsafe_addConnection(self, con): self._connections.append(con) def _threadsafe_getConnection(self): self._lock.acquire() try: con = PooledConnection(self, self._connections[self._nextCon]) self._nextCon = self._nextCon + 1 if self._nextCon >= len(self._connections): self._nextCon = 0 return con finally: self._lock.release() def _threadsafe_returnConnection(self, con): return # These functions are used with DB modules that have connection level threadsafety, like PostgreSQL. # def _unthreadsafe_addConnection(self, con): self._queue.put(con) def _unthreadsafe_getConnection(self): return PooledConnection(self, self._queue.get()) def _unthreadsafe_returnConnection(self, conpool): """ This should never be called explicitly outside of this module. """ self.addConnection(conpool._con)