Commit 59a35208 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Refactored database exceptions wrapping.

Squashed commit of the following:

commit 2181d833ed1a2e422494738dcef311164c4e097e
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Wed Feb 27 14:28:39 2013 +0100

    Fixed #15901 -- Wrapped all PEP-249 exceptions.

commit 5476a5d93c19aa2f928c497d39ce6e33f52694e2
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Tue Feb 26 17:26:52 2013 +0100

    Added PEP 3134 exception chaining.

    Thanks Jacob Kaplan-Moss for the suggestion.

commit 9365fad0a650328002fb424457d675a273c95802
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Tue Feb 26 17:13:49 2013 +0100

    Improved API for wrapping database errors.

    Thanks Alex Gaynor for the proposal.

commit 1b463b765f2826f73a8d9266795cd5da4f8d5e9e
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Tue Feb 26 15:00:39 2013 +0100

    Removed redundant exception wrapping.

    This is now taken care of by the cursor wrapper.

commit 524bc7345a724bf526bdd2dd1bcf5ede67d6bb5c
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Tue Feb 26 14:55:10 2013 +0100

    Wrapped database exceptions in the base backend.

    This covers the most common PEP-249 APIs:
    - Connection APIs: close(), commit(), rollback(), cursor()
    - Cursor APIs: callproc(), close(), execute(), executemany(),
      fetchone(), fetchmany(), fetchall(), nextset().

    Fixed #19920.

commit a66746bb5f0839f35543222787fce3b6a0d0a3ea
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date:   Tue Feb 26 14:53:34 2013 +0100

    Added a wrap_database_exception context manager and decorator.

    It re-throws backend-specific exceptions using Django's common wrappers.
parent 50328f0a
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
from django.conf import settings
from django.core import signals
from django.core.exceptions import ImproperlyConfigured
from django.db.utils import (ConnectionHandler, ConnectionRouter,
    load_backend, DEFAULT_DB_ALIAS, DatabaseError, IntegrityError)
from django.db.utils import (DEFAULT_DB_ALIAS,
    DataError, OperationalError, IntegrityError, InternalError,
    ProgrammingError, NotSupportedError, DatabaseError,
    InterfaceError, Error,
    load_backend, ConnectionHandler, ConnectionRouter)

__all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
    'IntegrityError', 'DEFAULT_DB_ALIAS')
+23 −10
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ from django.db import DEFAULT_DB_ALIAS
from django.db.backends.signals import connection_created
from django.db.backends import util
from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseErrorWrapper
from django.utils.functional import cached_property
from django.utils.importlib import import_module
from django.utils import six
@@ -57,6 +58,9 @@ class BaseDatabaseWrapper(object):
    def __hash__(self):
        return hash(self.alias)

    def wrap_database_errors(self):
        return DatabaseErrorWrapper(self.Database)

    def get_connection_params(self):
        raise NotImplementedError

@@ -70,6 +74,7 @@ class BaseDatabaseWrapper(object):
        raise NotImplementedError

    def _cursor(self):
        with self.wrap_database_errors():
            if self.connection is None:
                conn_params = self.get_connection_params()
                self.connection = self.get_new_connection(conn_params)
@@ -79,12 +84,19 @@ class BaseDatabaseWrapper(object):

    def _commit(self):
        if self.connection is not None:
            with self.wrap_database_errors():
                return self.connection.commit()

    def _rollback(self):
        if self.connection is not None:
            with self.wrap_database_errors():
                return self.connection.rollback()

    def _close(self):
        if self.connection is not None:
            with self.wrap_database_errors():
                return self.connection.close()

    def _enter_transaction_management(self, managed):
        """
        A hook for backend-specific changes required when entering manual
@@ -333,8 +345,9 @@ class BaseDatabaseWrapper(object):

    def close(self):
        self.validate_thread_sharing()
        if self.connection is not None:
            self.connection.close()
        try:
            self._close()
        finally:
            self.connection = None
        self.set_clean()

+4 −10
Original line number Diff line number Diff line
@@ -116,30 +116,22 @@ class CursorWrapper(object):
    def execute(self, query, args=None):
        try:
            return self.cursor.execute(query, args)
        except Database.IntegrityError as e:
            six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
        except Database.OperationalError as e:
            # Map some error codes to IntegrityError, since they seem to be
            # misclassified and Django would prefer the more logical place.
            if e[0] in self.codes_for_integrityerror:
                six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
        except Database.DatabaseError as e:
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
            raise

    def executemany(self, query, args):
        try:
            return self.cursor.executemany(query, args)
        except Database.IntegrityError as e:
            six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
        except Database.OperationalError as e:
            # Map some error codes to IntegrityError, since they seem to be
            # misclassified and Django would prefer the more logical place.
            if e[0] in self.codes_for_integrityerror:
                six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
        except Database.DatabaseError as e:
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
            raise

    def __getattr__(self, attr):
        if attr in self.__dict__:
@@ -391,6 +383,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        'iendswith': 'LIKE %s',
    }

    Database = Database

    def __init__(self, *args, **kwargs):
        super(DatabaseWrapper, self).__init__(*args, **kwargs)

+5 −11
Original line number Diff line number Diff line
@@ -501,6 +501,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        'iendswith': "LIKEC UPPER(%s) ESCAPE '\\'",
    })

    Database = Database

    def __init__(self, *args, **kwargs):
        super(DatabaseWrapper, self).__init__(*args, **kwargs)

@@ -604,10 +606,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        if self.connection is not None:
            try:
                return self.connection.commit()
            except Database.IntegrityError as e:
                # In case cx_Oracle implements (now or in a future version)
                # raising this specific exception
                six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
            except Database.DatabaseError as e:
                # cx_Oracle 5.0.4 raises a cx_Oracle.DatabaseError exception
                # with the following attributes and values:
@@ -620,7 +618,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
                if hasattr(x, 'code') and hasattr(x, 'message') \
                   and x.code == 2091 and 'ORA-02291' in x.message:
                    six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
                six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
                raise

    @cached_property
    def oracle_version(self):
@@ -760,13 +758,11 @@ class FormatStylePlaceholderCursor(object):
        self._guess_input_sizes([params])
        try:
            return self.cursor.execute(query, self._param_generator(params))
        except Database.IntegrityError as e:
            six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
        except Database.DatabaseError as e:
            # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400.
            if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError):
                six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
            raise

    def executemany(self, query, params=None):
        # cx_Oracle doesn't support iterators, convert them to lists
@@ -789,13 +785,11 @@ class FormatStylePlaceholderCursor(object):
        try:
            return self.cursor.executemany(query,
                                [self._param_generator(p) for p in formatted])
        except Database.IntegrityError as e:
            six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
        except Database.DatabaseError as e:
            # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400.
            if hasattr(e.args[0], 'code') and e.args[0].code == 1400 and not isinstance(e, IntegrityError):
                six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])
            raise

    def fetchone(self):
        row = self.cursor.fetchone()
+3 −42
Original line number Diff line number Diff line
@@ -40,40 +40,6 @@ def utc_tzinfo_factory(offset):
        raise AssertionError("database connection isn't set to UTC")
    return utc

class CursorWrapper(object):
    """
    A thin wrapper around psycopg2's normal cursor class so that we can catch
    particular exception instances and reraise them with the right types.
    """

    def __init__(self, cursor):
        self.cursor = cursor

    def execute(self, query, args=None):
        try:
            return self.cursor.execute(query, args)
        except Database.IntegrityError as e:
            six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
        except Database.DatabaseError as e:
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])

    def executemany(self, query, args):
        try:
            return self.cursor.executemany(query, args)
        except Database.IntegrityError as e:
            six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
        except Database.DatabaseError as e:
            six.reraise(utils.DatabaseError, utils.DatabaseError(*tuple(e.args)), sys.exc_info()[2])

    def __getattr__(self, attr):
        if attr in self.__dict__:
            return self.__dict__[attr]
        else:
            return getattr(self.cursor, attr)

    def __iter__(self):
        return iter(self.cursor)

class DatabaseFeatures(BaseDatabaseFeatures):
    needs_datetime_string_cast = False
    can_return_id_from_insert = True
@@ -106,6 +72,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        'iendswith': 'LIKE UPPER(%s)',
    }

    Database = Database

    def __init__(self, *args, **kwargs):
        super(DatabaseWrapper, self).__init__(*args, **kwargs)

@@ -207,7 +175,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
    def create_cursor(self):
        cursor = self.connection.cursor()
        cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
        return CursorWrapper(cursor)
        return cursor

    def _enter_transaction_management(self, managed):
        """
@@ -245,10 +213,3 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        if ((self.transaction_state and self.transaction_state[-1]) or
                not self.features.uses_autocommit):
            super(DatabaseWrapper, self).set_dirty()

    def _commit(self):
        if self.connection is not None:
            try:
                return self.connection.commit()
            except Database.IntegrityError as e:
                six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
Loading