Commits

jason kirtland committed e5cc9f4

Added an exception hierarchy shadowing DB-API exc types
No more generic SQLErrors wrappers- the shadow type matching the DB-API error is raised. [ticket:706]
SQLError is now (also) DBAPIError.
DBAPIError and subtype constructors will refuse to wrap a SystemExit or KeyboardInterrupt, returningthe original interrupt exception instead of a new instance. [ticket:689]
Added a passthroughs for SE/KI exceptions in a couple except-and-discard situations

Comments (0)

Files changed (5)

lib/sqlalchemy/engine/base.py

                 rec = (type, type.dialect_impl(self.dialect), i)
 
                 if rec[0] is None:
-                    raise exceptions.DBAPIError("None for metadata " + colname)
+                    raise exceptions.InvalidRequestError(
+                        "None for metadata " + colname)
                 if self.__props.setdefault(colname.lower(), rec) is not rec:
                     self.__props[colname.lower()] = (type, ResultProxy.AmbiguousColumn(colname), 0)
                 self.__keys.append(colname)

lib/sqlalchemy/engine/default.py

             # to appropriate character upon compilation
             self.positional = True
         else:
-            raise exceptions.DBAPIError("Unsupported paramstyle '%s'" % self._paramstyle)
+            raise exceptions.InvalidRequestError(
+                "Unsupported paramstyle '%s'" % self._paramstyle)
 
     def _get_ischema(self):
         if self._ischema is None:

lib/sqlalchemy/engine/strategies.py

                 try:
                     return dbapi.connect(*cargs, **cparams)
                 except Exception, e:
-                    raise exceptions.DBAPIError("Connection failed", e)
+                    raise exceptions.DBAPIError(None, None, e)
             creator = kwargs.pop('creator', connect)
 
             poolclass = (kwargs.pop('poolclass', None) or

lib/sqlalchemy/exceptions.py

 class SQLAlchemyError(Exception):
     """Generic error class."""
 
-    pass
-
-class SQLError(SQLAlchemyError):
-    """Raised when the execution of a SQL statement fails.
-
-    Includes accessors for the underlying exception, as well as the
-    SQL and bind parameters.
-    """
-
-    def __init__(self, statement, params, orig):
-        SQLAlchemyError.__init__(self, "(%s) %s"% (orig.__class__.__name__, str(orig)))
-        self.statement = statement
-        self.params = params
-        self.orig = orig
-
-    def __str__(self):
-        return SQLAlchemyError.__str__(self) + " " + repr(self.statement) + " " + repr(self.params)
 
 class ArgumentError(SQLAlchemyError):
     """Raised for all those conditions where invalid arguments are
     construction time state errors.
     """
 
-    pass
 
 class CompileError(SQLAlchemyError):
     """Raised when an error occurs during SQL compilation"""
-    
-    pass
-    
+        
+
 class TimeoutError(SQLAlchemyError):
     """Raised when a connection pool times out on getting a connection."""
 
-    pass
 
 class ConcurrentModificationError(SQLAlchemyError):
     """Raised when a concurrent modification condition is detected."""
 
-    pass
 
 class CircularDependencyError(SQLAlchemyError):
     """Raised by topological sorts when a circular dependency is detected"""
-    pass
     
+
 class FlushError(SQLAlchemyError):
     """Raised when an invalid condition is detected upon a ``flush()``."""
-    pass
+
 
 class InvalidRequestError(SQLAlchemyError):
     """SQLAlchemy was asked to do something it can't do, return
     This error generally corresponds to runtime state errors.
     """
 
-    pass
 
 class NoSuchTableError(InvalidRequestError):
     """SQLAlchemy was asked to load a table's definition from the
     database, but the table doesn't exist.
     """
 
-    pass
 
 class AssertionError(SQLAlchemyError):
     """Corresponds to internal state being detected in an invalid state."""
 
-    pass
 
 class NoSuchColumnError(KeyError, SQLAlchemyError):
     """Raised by ``RowProxy`` when a nonexistent column is requested from a row."""
 
-    pass
-
-class DBAPIError(SQLAlchemyError):
-    """Something weird happened with a particular DBAPI version."""
-
-    def __init__(self, message, orig):
-        SQLAlchemyError.__init__(self, "(%s) (%s) %s"% (message, orig.__class__.__name__, str(orig)))
-        self.orig = orig
 
 class DisconnectionError(SQLAlchemyError):
     """Raised within ``Pool`` when a disconnect is detected on a raw DBAPI connection."""
-    pass
+
+
+class DBAPIError(SQLAlchemyError):
+    """Raised when the execution of a database operation fails.
+
+    ``DBAPIError`` wraps exceptions raised by the DB-API underlying the
+    database operation.  Driver-specific implementations of the standard
+    DB-API exception types are wrapped by matching sub-types of SQLAlchemy's
+    ``DBAPIError`` when possible.  DB-API's ``Error`` type maps to
+    ``DBAPIError`` in SQLAlchemy, otherwise the names are identical.  Note
+    that there is no guarantee that different DB-API implementations will
+    raise the same exception type for any given error condition.
+
+    If the error-raising operation occured in the execution of a SQL
+    statement, that statement and its parameters will be available on
+    the exception object in the ``statement`` and ``params`` attributes.
+
+    The wrapped exception object is available in the ``orig`` attribute.
+    Its type and properties are DB-API implementation specific.  
+    """
+
+    def __new__(cls, statement, params, orig, *args, **kw):
+        # Don't ever wrap these, just return them directly as if
+        # DBAPIError didn't exist.
+        if isinstance(orig, (KeyboardInterrupt, SystemExit)):
+            return orig
+        
+        if orig is not None:
+            name, glob = type(orig).__name__, globals()
+            if name in glob and issubclass(glob[name], DBAPIError):
+                cls = glob[name]
+            
+        return SQLAlchemyError.__new__(cls, statement, params, orig,
+                                       *args, **kw)
+
+    def __init__(self, statement, params, orig):
+        SQLAlchemyError.__init__(self, "(%s) %s" %
+                                 (orig.__class__.__name__, str(orig)))
+        self.statement = statement
+        self.params = params
+        self.orig = orig
+
+    def __str__(self):
+        return ' '.join([SQLAlchemyError.__str__(self),
+                         repr(self.statement), repr(self.params)])
+
+
+# As of 0.4, SQLError is now DBAPIError
+SQLError = DBAPIError
+
+class InterfaceError(DBAPIError):
+    """Wraps a DB-API InterfaceError."""
+
+class DatabaseError(DBAPIError):
+    """Wraps a DB-API DatabaseError."""
+
+class DataError(DatabaseError):
+    """Wraps a DB-API DataError."""
+
+class OperationalError(DatabaseError):
+    """Wraps a DB-API OperationalError."""
+
+class IntegrityError(DatabaseError):
+    """Wraps a DB-API IntegrityError."""
+
+class InterfaceError(DatabaseError):
+    """Wraps a DB-API InterfaceError."""
+
+class ProgrammingError(DatabaseError):
+    """Wraps a DB-API ProgrammingError."""
+
+class NotSupportedError(DatabaseError):
+    """Wraps a DB-API NotSupportedError."""

lib/sqlalchemy/pool.py

             self.connection.close()
         except Exception, e:
             self.__pool.log("Connection %s threw an error on close: %s" % (repr(self.connection), str(e)))
+            if isinstance(e, (SystemExit, KeyboardInterrupt)):
+                raise
 
     def __connect(self):
         try:
             except Exception, e:
                 if self._connection_record is not None:
                     self._connection_record.invalidate(e=e)
+                if isinstance(e, (SystemExit, KeyboardInterrupt)):
+                    raise
         if self._connection_record is not None:
             if self._pool.echo:
                 self._pool.log("Connection %s being returned to pool" % repr(self.connection))
             self.cursor.close()
         except Exception, e:
             self.__parent._logger.warn("Error closing cursor: " + str(e))
+            if isinstance(e, (SystemExit, KeyboardInterrupt)):
+                raise
 
     def __getattr__(self, key):
         return getattr(self.cursor, key)
         for key, conn in self._conns.items():
             try:
                 conn.close()
+            except (SystemExit, KeyboardInterrupt):
+                raise
             except:
-                # sqlite won't even let you close a conn from a thread that didn't create it
+                # sqlite won't even let you close a conn from a thread 
+                # that didn't create it
                 pass
             del self._conns[key]