1
0
mirror of https://github.com/django/django.git synced 2025-07-05 02:09:13 +00:00

[multi-db] Fixed bugs in connection handling and test database setup. All tests now pass for postgres backend. Still failures for mysql and sqlite3, others unknown.

git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3768 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jason Pellerin 2006-09-17 19:12:04 +00:00
parent ecb5b81e0d
commit 27bcc64ac5
3 changed files with 88 additions and 49 deletions

View File

@ -14,6 +14,8 @@ __all__ = ('backend', 'connection', 'DatabaseError')
# singleton to represent the default connection in connections # singleton to represent the default connection in connections
class dummy(object): class dummy(object):
def __repr__(self):
return self.__str__()
def __str__(self): def __str__(self):
return '<default>' return '<default>'
_default = dummy() _default = dummy()
@ -28,22 +30,12 @@ if not settings.DATABASE_ENGINE:
settings.DATABASE_ENGINE = 'dummy' settings.DATABASE_ENGINE = 'dummy'
def connect(settings): def connect(settings, **kw):
"""Connect to the database specified in settings. Returns a """Connect to the database specified in settings. Returns a
ConnectionInfo on success, raises ImproperlyConfigured if the ConnectionInfo on success, raises ImproperlyConfigured if the
settings don't specify a valid database connection. settings don't specify a valid database connection.
""" """
info = ConnectionInfo(settings) return ConnectionInfo(settings, **kw)
# 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)
return info
class ConnectionInfo(object): class ConnectionInfo(object):
@ -52,7 +44,8 @@ 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, settings=None): def __init__(self, settings=None, **kw):
super(ConnectionInfo, self).__init__(**kw)
if settings is None: if settings is None:
from django.conf import settings from django.conf import settings
self.settings = settings self.settings = settings
@ -60,6 +53,14 @@ class ConnectionInfo(object):
self.connection = self.backend.DatabaseWrapper(settings) self.connection = self.backend.DatabaseWrapper(settings)
self.DatabaseError = self.backend.DatabaseError self.DatabaseError = self.backend.DatabaseError
# Register an event that closes the database connection
# when a Django request is finished.
dispatcher.connect(self.close, signal=signals.request_finished)
# Register an event that resets connection.queries
# when a Django request is started.
dispatcher.connect(self.reset_queries, signal=signals.request_started)
def __repr__(self): def __repr__(self):
return "Connection: %r (ENGINE=%s NAME=%s)" \ return "Connection: %r (ENGINE=%s NAME=%s)" \
% (self.connection, % (self.connection,
@ -123,8 +124,14 @@ class LazyConnectionManager(object):
self.local = local() self.local = local()
self.local.connections = {} self.local.connections = {}
# Reset connections on request finish, to make sure each request can
# load the correct connections for its settings
dispatcher.connect(self.reset, signal=signals.request_finished)
def __iter__(self): def __iter__(self):
return self.local.connections.keys() # Iterates only over *active* connections, not all possible
# connections
return iter(self.local.connections.keys())
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.local.connections, attr) return getattr(self.local.connections, attr)
@ -177,9 +184,26 @@ class LazyConnectionManager(object):
cnx[name] = connect(database) cnx[name] = connect(database)
return cnx[name] return cnx[name]
def reset(self): def items(self):
self.local.connections = {} # Iterates over *all possible* connections
items = []
for key in self.keys():
items.append((key, self[key]))
return items
def keys(self):
# Iterates over *all possible* connections
keys = [_default]
try:
keys.extend(settings.OTHER_DATABASES.keys())
except AttributeError:
pass
return keys
def reset(self):
if not hasattr(self.local, 'connections'):
return
self.local.connections = {}
def model_connection_name(klass): def model_connection_name(klass):
"""Get the connection name that a model is configured to use, with the """Get the connection name that a model is configured to use, with the
@ -263,9 +287,10 @@ class ConnectionInfoDescriptor(object):
return connections[model_connection_name(instance.model)] return connections[model_connection_name(instance.model)]
def reset(self): def reset(self):
if not hasattr(self.local, 'cnx'):
return
self.local.cnx = {} self.local.cnx = {}
class LocalizingProxy: class LocalizingProxy:
"""A lazy-initializing proxy. The proxied object is not """A lazy-initializing proxy. The proxied object is not
initialized until the first attempt to access it. This is used to initialized until the first attempt to access it. This is used to
@ -278,6 +303,13 @@ class LocalizingProxy:
self.__arg = arg self.__arg = arg
self.__kw = kw self.__kw = kw
# We need to clear out this thread's storage at the end of each
# request, in case new settings are loaded with the next
def reset(stor=storage, name=name):
if hasattr(stor, name):
delattr(stor, name)
dispatcher.connect(reset, signal=signals.request_finished)
def __getattr__(self, attr): def __getattr__(self, attr):
# Private (__*) attributes are munged # Private (__*) attributes are munged
if attr.startswith('_LocalizingProxy'): if attr.startswith('_LocalizingProxy'):
@ -295,7 +327,6 @@ class LocalizingProxy:
self.__dict__[attr] = val self.__dict__[attr] = val
return return
try: try:
print self.__storage, self.__name
stor = getattr(self.__storage, self.__name) stor = getattr(self.__storage, self.__name)
except AttributeError: except AttributeError:
stor = self.__func(*self.__arg) stor = self.__func(*self.__arg)
@ -327,11 +358,6 @@ runshell = LocalizingProxy('runshell', _local,
lambda: connections[_default].runshell) lambda: connections[_default].runshell)
# 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)
# 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():

View File

@ -1,6 +1,5 @@
import sys, time import sys, time
from django.conf import settings from django.conf import settings
from django.db import backend, connect, connection, connection_info, connections from django.db import backend, connect, connection, connection_info, connections
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.test import signals from django.test import signals
@ -94,21 +93,13 @@ def create_test_db(verbosity=1, autoclobber=False):
cursor = connection.cursor() cursor = connection.cursor()
# Fill OTHER_DATABASES with the TEST_DATABASES settings, # Fill OTHER_DATABASES with the TEST_DATABASES settings,
# and connect each named connection to the test database, using # and connect each named connection to the test database.
# a separate connection instance for each (so, eg, transactions don't
# collide)
test_databases = {} test_databases = {}
for db_name in settings.TEST_DATABASES: for db_name in settings.TEST_DATABASES:
if settings.DATABASE_ENGINE == 'sqlite3': db_st = {'DATABASE_NAME': TEST_DATABASE_NAME}
full_name = TEST_DATABASE_NAME
else:
full_name = TEST_DATABASE_NAME + db_name
db_st = {'DATABASE_NAME': full_name}
if db_name in settings.TEST_DATABASE_MODELS: if db_name in settings.TEST_DATABASE_MODELS:
db_st['MODELS'] = settings.TEST_DATABASE_MODELS.get(db_name, []) db_st['MODELS'] = settings.TEST_DATABASE_MODELS.get(db_name, [])
test_databases[db_name] = db_st test_databases[db_name] = db_st
connections[db_name] = connect(connection_info.settings)
connections[db_name].connection.cursor() # Initialize it
settings.OTHER_DATABASES = test_databases settings.OTHER_DATABASES = test_databases
def destroy_test_db(old_database_name, old_databases, verbosity=1): def destroy_test_db(old_database_name, old_databases, verbosity=1):
@ -118,15 +109,26 @@ def destroy_test_db(old_database_name, old_databases, verbosity=1):
# connected to it. # connected to it.
if verbosity >= 1: if verbosity >= 1:
print "Destroying test database..." print "Destroying test database..."
connection.close()
for cnx in connections.keys(): for cnx in connections.keys():
connections[cnx].close() connections[cnx].close()
connections.reset()
TEST_DATABASE_NAME = settings.DATABASE_NAME TEST_DATABASE_NAME = settings.DATABASE_NAME
if verbosity >= 2:
print "Closed connections to %s" % TEST_DATABASE_NAME
settings.DATABASE_NAME = old_database_name settings.DATABASE_NAME = old_database_name
if settings.DATABASE_ENGINE != "sqlite3": if settings.DATABASE_ENGINE != "sqlite3":
settings.OTHER_DATABASES = old_databases settings.OTHER_DATABASES = old_databases
for cnx in connections.keys(): for cnx in connections.keys():
try:
connections[cnx].connection.cursor() connections[cnx].connection.cursor()
except (KeyboardInterrupt, SystemExit):
raise
except:
pass
cursor = connection.cursor() cursor = connection.cursor()
_set_autocommit(connection) _set_autocommit(connection)
time.sleep(1) # To avoid "database is being accessed by other users" errors. time.sleep(1) # To avoid "database is being accessed by other users" errors.

View File

@ -84,22 +84,24 @@ Connection: ...
>>> connections['_b'] >>> connections['_b']
Connection: ... Connection: ...
# Let's see what connections are available.The default connection is # Let's see what connections are available. The default connection is always
# in there, but let's ignore it # included in connections as well, and may be accessed as connections[_default].
>>> non_default = connections.keys() >>> connection_names = connections.keys()
>>> non_default.remove(_default) >>> connection_names.sort()
>>> non_default.sort() >>> connection_names
>>> non_default [<default>, '_a', '_b']
['_a', '_b']
# Invalid connection names raise ImproperlyConfigured # Invalid connection names raise ImproperlyConfigured
>>> connections['bad'] >>> connections['bad']
Traceback (most recent call last): Traceback (most recent call last):
... ...
ImproperlyConfigured: No database connection 'bad' has been configured ImproperlyConfigured: No database connection 'bad' has been configured
# Models can access their connections through their managers # The model_connection_name() function will tell you the name of the
# connection that a model is configured to use.
>>> model_connection_name(Artist) >>> model_connection_name(Artist)
'_a' '_a'
>>> model_connection_name(Widget) >>> model_connection_name(Widget)
@ -116,6 +118,15 @@ True
>>> list(artists) >>> list(artists)
[<Artist: Paul Klee>] [<Artist: Paul Klee>]
# Models can access their connections through the db property of their
# default manager.
>>> paul = _[0]
>>> Artist.objects.db
Connection: ... (ENGINE=... NAME=...)
>>> paul._default_manager.db
Connection: ... (ENGINE=... NAME=...)
# When transactions are not managed, model save will commit only # When transactions are not managed, model save will commit only
# for the model's connection. # for the model's connection.