1
0
mirror of https://github.com/django/django.git synced 2025-07-05 02:09: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.core.exceptions import ImproperlyConfigured
from django.dispatch import dispatcher 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') __all__ = ('backend', 'connection', 'DatabaseError')
# singleton to represent the default connection in connections
_default = object()
if not settings.DATABASE_ENGINE: if not settings.DATABASE_ENGINE:
settings.DATABASE_ENGINE = 'dummy' settings.DATABASE_ENGINE = 'dummy'
@ -13,22 +23,7 @@ def connect(settings):
ConnectionInfo on succes, raises ImproperlyConfigured if the ConnectionInfo on succes, raises ImproperlyConfigured if the
settings don't specify a valid database connection. settings don't specify a valid database connection.
""" """
try: info = ConnectionInfo(settings)
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)
# Register an event that closes the database connection # Register an event that closes the database connection
# when a Django request is finished. # when a Django request is finished.
@ -47,11 +42,13 @@ class ConnectionInfo(object):
creation, introspection, and shell modules, closing the creation, introspection, and shell modules, closing the
connection, and resetting the connection's query log. connection, and resetting the connection's query log.
""" """
def __init__(self, backend, settings): def __init__(self, settings=None):
self.backend = backend if settings is None:
from django.conf import settings
self.settings = settings self.settings = settings
self.connection = backend.DatabaseWrapper(settings) self.backend = self.load_backend()
self.DatabaseError = backend.DatabaseError self.connection = self.backend.DatabaseWrapper(settings)
self.DatabaseError = self.backend.DatabaseError
def __repr__(self): def __repr__(self):
return "Connection: %r (ENGINE=%s NAME=%s)" \ return "Connection: %r (ENGINE=%s NAME=%s)" \
@ -71,6 +68,33 @@ class ConnectionInfo(object):
return __import__('django.db.backends.%s.creation' % return __import__('django.db.backends.%s.creation' %
self.settings.DATABASE_ENGINE, '', '', ['']) 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): def runshell(self):
__import__('django.db.backends.%s.client' % __import__('django.db.backends.%s.client' %
self.settings.DATABASE_ENGINE, '', '', ['']).runshell() self.settings.DATABASE_ENGINE, '', '', ['']).runshell()
@ -85,53 +109,137 @@ class LazyConnectionManager(object):
they are requested. they are requested.
""" """
def __init__(self): def __init__(self):
self._connections = {} self.local = local()
self.local.connections = {}
def __iter__(self): def __iter__(self):
return self._connections.keys() return self.local.connections.keys()
def __getattr__(self, attr): def __getattr__(self, attr):
# use __dict__ to avoid getattr() loop return getattr(self.local.connections, attr)
return getattr(self.__dict__['_connections'], attr)
def __getitem__(self, k): def __getitem__(self, k):
try: try:
return self.__dict__['_connections'][k] return self.local.connections[k]
except KeyError: except KeyError:
return self.connect(k) return self.connect(k)
def __setitem__(self, k, v): def __setitem__(self, k, v):
self.__dict__['_connections'][k] = v self.local.connections[k] = v
def connect(self, name): def connect(self, name):
"""Return the connection with this name in """Return the connection with this name in
settings.DATABASES. Creates the connection if it doesn't yet settings.OTHER_DATABASES. Creates the connection if it doesn't yet
exist. Reconnects if it does. 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: cnx = self.local.connections
self._connections[name].close() 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: try:
info = settings.DATABASES[name] info = settings.OTHER_DATABASES[name]
except KeyError: except KeyError:
raise ImproperlyConfigured, \ raise ImproperlyConfigured, \
"No database connection '%s' has been configured" % name "No database connection '%s' has been configured" % name
except AttributeError: except AttributeError:
raise ImproperlyConfigured, \ 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 # 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) database = UserSettingsHolder(settings)
for k, v in info.items(): for k, v in info.items():
setattr(database, k, v) setattr(database, k, v)
self._connections[name] = connect(database) cnx[name] = connect(database)
return self._connections[name] 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 # Backwards compatibility: establish the default connection and set the
# default connection properties at module level # default connection properties at module level
connection_info = connect(settings) connection_info = DefaultConnectionInfoProxy()
(connection, DatabaseError, backend, get_introspection_module, (connection, DatabaseError, backend, get_introspection_module,
get_creation_module, runshell) = (connection_info.connection, get_creation_module, runshell) = (connection_info.connection,
connection_info.DatabaseError, connection_info.DatabaseError,
@ -143,6 +251,13 @@ connection_info = connect(settings)
# Create a manager for named connections # Create a manager for named connections
connections = LazyConnectionManager() 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 # Register an event that rolls back all connections
# when a Django request has an exception. # when a Django request has an exception.
def _rollback_on_exception(): def _rollback_on_exception():