1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

[multi-db] Refactored connection handling to correct bugs in original

design relating to thread and request isolation. Changed name of 
optional named databases attribute in settings to 
settings.OTHER_DATABASES.


git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3334 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jason Pellerin 2006-07-11 20:37:07 +00:00
parent c0132e88f6
commit 97d1f60d77

View File

@ -3,8 +3,18 @@ from django.core import signals
from django.core.exceptions import ImproperlyConfigured
from django.dispatch import dispatcher
try:
# Only exists in Python 2.4+
from threading import local
except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
__all__ = ('backend', 'connection', 'DatabaseError')
# singleton to represent the default connection in connections
_default = object()
if not settings.DATABASE_ENGINE:
settings.DATABASE_ENGINE = 'dummy'
@ -13,22 +23,7 @@ def connect(settings):
ConnectionInfo on succes, raises ImproperlyConfigured if the
settings don't specify a valid database connection.
"""
try:
backend = __import__('django.db.backends.%s.base' % settings.DATABASE_ENGINE, '', '', [''])
except ImportError, e:
# The database backend wasn't found. Display a helpful error message
# listing all possible database backends.
import os
backend_dir = os.path.join(__path__[0], 'backends')
available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
available_backends.sort()
if settings.DATABASE_ENGINE not in available_backends:
raise ImproperlyConfigured, "%r isn't an available database backend. vailable options are: %s" % \
(settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
else:
raise # If there's some other error, this must be an error in Django itself.
info = ConnectionInfo(backend, settings)
info = ConnectionInfo(settings)
# Register an event that closes the database connection
# when a Django request is finished.
@ -47,11 +42,13 @@ class ConnectionInfo(object):
creation, introspection, and shell modules, closing the
connection, and resetting the connection's query log.
"""
def __init__(self, backend, settings):
self.backend = backend
def __init__(self, settings=None):
if settings is None:
from django.conf import settings
self.settings = settings
self.connection = backend.DatabaseWrapper(settings)
self.DatabaseError = backend.DatabaseError
self.backend = self.load_backend()
self.connection = self.backend.DatabaseWrapper(settings)
self.DatabaseError = self.backend.DatabaseError
def __repr__(self):
return "Connection: %r (ENGINE=%s NAME=%s)" \
@ -71,6 +68,33 @@ class ConnectionInfo(object):
return __import__('django.db.backends.%s.creation' %
self.settings.DATABASE_ENGINE, '', '', [''])
def load_backend(self):
try:
backend = __import__('django.db.backends.%s.base' %
self.settings.DATABASE_ENGINE, '', '', [''])
except ImportError, e:
# The database backend wasn't found. Display a helpful error
# message listing all possible database backends.
import os
backend_dir = os.path.join(__path__[0], 'backends')
available_backends = [f for f in os.listdir(backend_dir)
if not f.startswith('_') \
and not f.startswith('.') \
and not f.endswith('.py') \
and not f.endswith('.pyc')]
available_backends.sort()
if settings.DATABASE_ENGINE not in available_backends:
raise ImproperlyConfigured, \
"%r isn't an available database backend. "\
"Available options are: %s" % \
(settings.DATABASE_ENGINE,
", ".join(map(repr, available_backends)))
else:
# If there's some other error, this must be an error
# in Django itself.
raise
return backend
def runshell(self):
__import__('django.db.backends.%s.client' %
self.settings.DATABASE_ENGINE, '', '', ['']).runshell()
@ -79,70 +103,161 @@ class ConnectionInfo(object):
"""Reset log of queries executed by connection"""
self.connection.queries = []
class LazyConnectionManager(object):
"""Manages named connections lazily, instantiating them as
they are requested.
"""
def __init__(self):
self._connections = {}
self.local = local()
self.local.connections = {}
def __iter__(self):
return self._connections.keys()
return self.local.connections.keys()
def __getattr__(self, attr):
# use __dict__ to avoid getattr() loop
return getattr(self.__dict__['_connections'], attr)
return getattr(self.local.connections, attr)
def __getitem__(self, k):
try:
return self.__dict__['_connections'][k]
return self.local.connections[k]
except KeyError:
return self.connect(k)
def __setitem__(self, k, v):
self.__dict__['_connections'][k] = v
self.local.connections[k] = v
def connect(self, name):
"""Return the connection with this name in
settings.DATABASES. Creates the connection if it doesn't yet
exist. Reconnects if it does.
settings.OTHER_DATABASES. Creates the connection if it doesn't yet
exist. Reconnects if it does. If the name requested is the default
connection (a singleton defined in django.db), then the default
connection is returned.
"""
if name in self._connections:
self._connections[name].close()
cnx = self.local.connections
if name in cnx:
cnx[name].close()
if name is _default:
# get the default connection from connection_info
if connection_info.local.db is None:
connection_info.init_connection()
cnx[name] = connection_info.local.db
return cnx[name]
try:
info = settings.DATABASES[name]
info = settings.OTHER_DATABASES[name]
except KeyError:
raise ImproperlyConfigured, \
"No database connection '%s' has been configured" % name
except AttributeError:
raise ImproperlyConfigured, \
"No DATABASES in settings."
"No OTHER_DATABASES in settings."
# In settings it's a dict, but connect() needs an object
# In settings it's a dict, but connect() needs an object:
# pass global settings so that the default connection settings
# can be defaults for the named connections
# can be defaults for the named connections.
database = UserSettingsHolder(settings)
for k, v in info.items():
setattr(database, k, v)
self._connections[name] = connect(database)
return self._connections[name]
cnx[name] = connect(database)
return cnx[name]
def reset(self):
self.local.connections = {}
class _proxy:
"""A lazy-initializing proxy. The proxied object is not
initialized until the first attempt to access it.
"""
def __init__(self, init_obj):
self.__dict__['_obj'] = None
self.__dict__['_init_obj'] = init_obj
def __getattr__(self, attr):
if self.__dict__['_obj'] is None:
self.__dict__['_obj'] = self.__dict__['_init_obj']()
return getattr(self.__dict__['_obj'], attr)
def __setattr__(self, attr, val):
if self.__dict__['_obj'] is None:
self.__dict__['_obj'] = self.__dict__['_init_obj']()
setattr(self.__dict__['_obj'], attr, val)
class DefaultConnectionInfoProxy(object):
"""Holder for proxy objects that will connect to the current
default connection when used. Mimics the interface of a ConnectionInfo.
"""
def __init__(self):
self.local = local()
self.local.db = None
self.connection = _proxy(self.get_connection)
self.DatabaseError = _proxy(self.get_database_error)
self.backend = _proxy(self.get_backend)
self.get_introspection_module = _proxy(self.get_introspection_module)
self.get_creation_module = _proxy(self.get_creation_module)
self.runshell = _proxy(self.get_runshell)
def init_connection(self):
from django.conf import settings
self.local.db = connect(settings)
def get_backend(self):
if self.local.db is None:
self.init_connection()
return self.local.db.backend
def get_connection(self):
if self.local.db is None:
self.init_connection()
return self.local.db.connection
def get_database_error(self):
if self.local.db is None:
self.init_connection()
return self.local.db.DatabaseError
def get_introspection_module(self):
if self.local.db is None:
self.init_connection()
return self.local.db.get_introspection_module
def get_creation_module(self):
if self.local.db is None:
self.init_connection()
return self.local.db.get_creation_module
def get_runshell(self):
if self.local.db is None:
self.init_connection()
return self.local.db.runshell
def close(self):
self.local.db = None
# Backwards compatibility: establish the default connection and set the
# default connection properties at module level
connection_info = connect(settings)
connection_info = DefaultConnectionInfoProxy()
(connection, DatabaseError, backend, get_introspection_module,
get_creation_module, runshell) = (connection_info.connection,
connection_info.DatabaseError,
connection_info.backend,
connection_info.get_introspection_module,
connection_info.get_introspection_module,
connection_info.get_creation_module,
connection_info.runshell)
# Create a manager for named connections
connections = LazyConnectionManager()
# Reset connections on request finish, to make sure each request can
# load the correct connections for its settings
dispatcher.connect(connections.reset, signal=signals.request_finished)
# Clear the default connection on request finish also
dispatcher.connect(connection_info.close, signal=signals.request_finished)
# Register an event that rolls back all connections
# when a Django request has an exception.
def _rollback_on_exception():