Author: jpellerin
Date: 2006-07-11 15:37:07 -0500 (Tue, 11 Jul 2006)
New Revision: 3334

Modified:
   django/branches/multiple-db-support/django/db/__init__.py
Log:
[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.


Modified: django/branches/multiple-db-support/django/db/__init__.py
===================================================================
--- django/branches/multiple-db-support/django/db/__init__.py   2006-07-11 
19:08:24 UTC (rev 3333)
+++ django/branches/multiple-db-support/django/db/__init__.py   2006-07-11 
20:37:07 UTC (rev 3334)
@@ -3,8 +3,18 @@
 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,23 +23,8 @@
     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(settings)
 
-    info = ConnectionInfo(backend, settings)
-
     # Register an event that closes the database connection
     # when a Django request is finished.
     dispatcher.connect(info.close, signal=signals.request_finished)
@@ -47,11 +42,13 @@
     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 @@
         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 @@
         """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():


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates
-~----------~----~----~----~------~----~------~--~---

Reply via email to