diff --git a/django/db/__init__.py b/django/db/__init__.py index f21ae6a727..f519b5bea9 100644 --- a/django/db/__init__.py +++ b/django/db/__init__.py @@ -1,5 +1,6 @@ -from django.conf import settings +from django.conf import settings, UserSettingsHolder from django.core import signals +from django.core.exceptions import ImproperlyConfigured from django.dispatch import dispatcher __all__ = ('backend', 'connection', 'DatabaseError') @@ -7,42 +8,148 @@ __all__ = ('backend', 'connection', 'DatabaseError') if not settings.DATABASE_ENGINE: settings.DATABASE_ENGINE = 'dummy' -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. - from django.core.exceptions import ImproperlyConfigured - 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. +def connect(settings): + """Connect to the database specified in settings. Returns a + 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. -get_introspection_module = lambda: __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, '', '', ['']) -get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, '', '', ['']) -runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, '', '', ['']).runshell() + info = ConnectionInfo(backend, settings) -connection = backend.DatabaseWrapper() -DatabaseError = backend.DatabaseError + # Register an event that closes the database connection + # when a Django request is finished. + dispatcher.connect(info.close, signal=signals.request_finished) + + # Register an event that resets connection.queries + # when a Django request is started. + dispatcher.connect(info.reset_queries, signal=signals.request_started) -# Register an event that closes the database connection -# when a Django request is finished. -dispatcher.connect(connection.close, signal=signals.request_finished) + return info -# Register an event that resets connection.queries -# when a Django request is started. -def reset_queries(): - connection.queries = [] -dispatcher.connect(reset_queries, signal=signals.request_started) + +class ConnectionInfo(object): + """Encapsulates all information about a connection and the backend + to which it belongs. Provides methods for loading backend + creation, introspection, and shell modules, closing the + connection, and resetting the connection's query log. + """ + def __init__(self, backend, settings): + self.backend = backend + self.settings = settings + self.connection = backend.DatabaseWrapper(settings) + self.DatabaseError = backend.DatabaseError -# Register an event that rolls back the connection + def __repr__(self): + return "Connection: %r (ENGINE=%s NAME=%s)" \ + % (self.connection, + self.connection.settings.DATABASE_ENGINE, + self.connection.settings.DATABASE_NAME) + + def close(self): + """Close connection""" + self.connection.close() + + def get_introspection_module(self): + return __import__('django.db.backends.%s.introspection' % + self.settings.DATABASE_ENGINE, '', '', ['']) + + def get_creation_module(self): + return __import__('django.db.backends.%s.creation' % + self.settings.DATABASE_ENGINE, '', '', ['']) + + def runshell(self): + __import__('django.db.backends.%s.client' % + self.settings.DATABASE_ENGINE, '', '', ['']).runshell() + + def reset_queries(self): + """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 = {} + + def __iter__(self): + return self._connections.keys() + + def __getattr__(self, attr): + # use __dict__ to avoid getattr() loop + return getattr(self.__dict__['_connections'], attr) + + def __getitem__(self, k): + try: + return self._connections[k] + except KeyError: + try: + return self.connect(k) + except KeyError: + raise ImproperlyConfigured, \ + "No database connection '%s' has been configured" % k + + 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. + """ + if name in self._connections: + self._connections[name].close() + try: + info = settings.DATABASES[name] + except KeyError: + raise ImproperlyConfigured, \ + "No database connection '%s' has been configured" % name + except AttributeError: + raise ImproperlyConfigured, \ + "No DATABASES in settings." + + # 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 + database = UserSettingsHolder(settings) + for k, v in info.items(): + setattr(database, k, v) + self._connections[name] = connect(database) + return self._connections[name] + + +# Backwards compatibility: establish the default connection and set the +# default connection properties at module level +connection_info = connect(settings) +(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_creation_module, + connection_info.runshell) + +# Create a manager for named connections +connections = LazyConnectionManager() + +# Register an event that rolls back all connections # when a Django request has an exception. def _rollback_on_exception(): from django.db import transaction transaction.rollback_unless_managed() -dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception) +dispatcher.connect(_rollback_on_exception, + signal=signals.got_request_exception) + + diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index afe2d19981..8844faf293 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -55,12 +55,13 @@ except ImportError: from django.utils._threading_local import local class DatabaseWrapper(local): - def __init__(self): + def __init__(self, settings): + self.settings = settings self.connection = None self.queries = [] def cursor(self): - from django.conf import settings + settings = self.settings if self.connection is None: if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '': from django.core.exceptions import ImproperlyConfigured diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index a522f24f2f..669a4fccf2 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -58,7 +58,8 @@ except ImportError: from django.utils._threading_local import local class DatabaseWrapper(local): - def __init__(self): + def __init__(self, settings): + self.settings = settings self.connection = None self.queries = [] @@ -73,7 +74,7 @@ class DatabaseWrapper(local): return False def cursor(self): - from django.conf import settings + settings = self.settings if not self._valid_connection(): kwargs = { 'user': settings.DATABASE_USER, diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 9943ac9610..3bbe174188 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -22,7 +22,8 @@ except ImportError: from django.utils._threading_local import local class DatabaseWrapper(local): - def __init__(self): + def __init__(self, settings): + self.settings = settings self.connection = None self.queries = [] @@ -30,7 +31,7 @@ class DatabaseWrapper(local): return self.connection is not None def cursor(self): - from django.conf import settings + settings = self.settings if not self._valid_connection(): if len(settings.DATABASE_HOST.strip()) == 0: settings.DATABASE_HOST = 'localhost' diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 5355781e81..f9372bf1f8 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -21,12 +21,13 @@ except ImportError: from django.utils._threading_local import local class DatabaseWrapper(local): - def __init__(self): + def __init__(self, settings): + self.settings = settings self.connection = None self.queries = [] def cursor(self): - from django.conf import settings + settings = self.settings if self.connection is None: if settings.DATABASE_NAME == '': from django.core.exceptions import ImproperlyConfigured diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 68452e1363..b277526b5a 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -34,12 +34,13 @@ except ImportError: from django.utils._threading_local import local class DatabaseWrapper(local): - def __init__(self): + def __init__(self, settings): + self.settings = settings self.connection = None self.queries = [] def cursor(self): - from django.conf import settings + settings = self.settings if self.connection is None: self.connection = Database.connect(settings.DATABASE_NAME, detect_types=Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES)