blob: 38f8c7d528d121102ea87ed5dff71e6f98696f4e [file] [log] [blame]
from django import db as django_db
from django.conf import settings
from django.core import signals
class ReadOnlyConnection(object):
"""
This class constructs a new connection to the DB using the read-only
credentials from settings. It reaches into some internals of
django.db.connection which are undocumented as far as I know, but I believe
it works across many, if not all, of the backends.
"""
_the_instance = None
# support singleton
@classmethod
def get_connection(cls):
if cls._the_instance is None:
cls._the_instance = ReadOnlyConnection()
return cls._the_instance
@classmethod
def set_globally_disabled(cls, disabled):
"""
When globally disabled, the ReadOnlyConnection will simply pass through
to the global Django connection.
"""
if disabled:
cls._the_instance = DummyReadOnlyConnection()
else:
cls._the_instance = None
def __init__(self):
self._connection = None
def _open_connection(self):
if self._connection is not None:
return
self._save_django_state()
self._connection = self._get_readonly_connection()
self._restore_django_state()
def _save_django_state(self):
self._old_connection = django_db.connection.connection
_default_db = settings.DATABASES['default']
self._old_host = _default_db['HOST']
self._old_username = _default_db['USER']
self._old_password = _default_db['PASSWORD']
def _restore_django_state(self):
django_db.connection.connection = self._old_connection
_default_db = settings.DATABASES['default']
_default_db['HOST'] = self._old_host
_default_db['USER'] = self._old_username
_default_db['PASSWORD'] = self._old_password
def _get_readonly_connection(self):
_default_db = settings.DATABASES['default']
_default_db['HOST'] = _default_db['READONLY_HOST']
_default_db['USER'] = _default_db['READONLY_USER']
_default_db['PASSWORD'] = _default_db['READONLY_PASSWORD']
reload(django_db)
# cursor() causes a new connection to be created
cursor = django_db.connection.cursor()
assert django_db.connection.connection is not None
return django_db.connection.connection
def set_django_connection(self):
assert (django_db.connection.connection != self._connection or
self._connection is None)
self._open_connection()
self._old_connection = django_db.connection.connection
django_db.connection.connection = self._connection
def unset_django_connection(self):
assert self._connection is not None
assert django_db.connection.connection == self._connection
django_db.connection.connection = self._old_connection
def cursor(self):
self._open_connection()
return self._connection.cursor()
def close(self):
if self._connection is not None:
assert django_db.connection.connection != self._connection
self._connection.close()
self._connection = None
class DummyReadOnlyConnection(object):
"""
A dummy version which passes queries straight to the global Django
connection.
"""
def __init__(self):
self._is_set = False
def set_django_connection(self):
assert not self._is_set
self._is_set = True
def unset_django_connection(self):
assert self._is_set
self._is_set = False
def cursor(self):
return django_db.connection.cursor()
def close(self):
pass
# convenience
def connection():
return ReadOnlyConnection.get_connection()
# close any open connection when request finishes
def _close_connection(**unused_kwargs):
connection().close()
signals.request_finished.connect(_close_connection)