Following the discussion we had about 2 months ago about having sqlobject 
raise a set of exceptions independent of the backend used, I asked a 
colleague of mine to implement this.
He did it for the mysql and sqlite backends, for which we have experience.
Not having any experience with the other backends, we left their 
implementation to someone who knows them and is interested in doing this.
Still the patch would give a good start in the direction of having 
sqlobject handle exceptions consistently without the need of prior 
knowledge about the backend that is used.

Below is the email my colleague tried to send to the mailing list 2 times 
without success. Hopefully this will go through.


----------  Forwarded Message  ----------

Subject: Database exceptions handling
Date: Wednesday 26 July 2006 08:03
From: Mircea Amarascu <[EMAIL PROTECTED]>
To: sqlobject-discuss@lists.sourceforge.net

Hello,

I've read the "Handling exceptions" topic on this mailing list
(http://www.mail-archive.com/sqlobject-discuss%40lists.sourceforge.net/ms
g00975.html) and decided to take a step further and try to add a database
 exception hierarchy to
SQLObject, as described in PEP249
(http://www.python.org/dev/peps/pep-0249/).

I've discovered that the Database API Specification v2.0 is followed by
both MySQL and
SQLite Python modules, so all that need to be done in this case was a
one-to-one mapping between
their exceptions and the SQLObject defined ones. I have no
experience in other database APIs (Postgres, Sybase, etc), so I haven't 
modified those backends..

The error message attached to the SQLObject database exception is
basically a string,
but it also contains the error code (if any), and the originating module
and exception,
in case these would need later inspection.

We could also add more specific exceptions derived from the base ones,
such as
DatabaseConnectionError(OperationalError), or
DuplicateEntryError(IntegrityError). In my
implementation I found it useful to have a DuplicateEntryError
exception, to know
exactly if an IntegrityError was raised when a duplicate key constraint
violation has occured.

I've attached the patch (diffed against svn revision 1827), please tell
me your opinion about it.

-------------------------------------------------------

-- 
Dan
Index: __init__.py
===================================================================
--- __init__.py (revision 1827)
+++ __init__.py (working copy)
@@ -5,6 +5,7 @@
 from joins import *
 from index import *
 from dbconnection import connectionForURI
+import dberrors
 
 ## Each of these imports allows the driver to install itself
 ## Then we set up some backward compatibility
Index: sqlite/sqliteconnection.py
===================================================================
--- sqlite/sqliteconnection.py  (revision 1827)
+++ sqlite/sqliteconnection.py  (working copy)
@@ -1,11 +1,20 @@
 from sqlobject.dbconnection import DBAPI
 from sqlobject.col import popKey
+from sqlobject.dberrors import *
 import thread
 
 sqlite = None
 using_sqlite2 = False
 sqlite2_Binary = None
 
+class ErrorMessage(str):
+    def __new__(cls, e):
+        obj = str.__new__(cls, e[0])
+        obj.code = None
+        obj.module = e.__module__
+        obj.exception = e.__class__.__name__
+        return obj
+
 class SQLiteConnection(DBAPI):
 
     supportTransactions = True
@@ -154,6 +163,36 @@
             return self._memoryConn
         return sqlite.connect(self.filename, **self._connOptions)
 
+    def _executeRetry(self, conn, cursor, query):
+        if self.debug:
+            self.printDebug(conn, query, 'QueryR')
+        try:
+            return cursor.execute(query)
+        except self.module.OperationalError, e:
+            raise OperationalError(ErrorMessage(e))
+        except self.module.IntegrityError, e:
+            msg = ErrorMessage(e)
+            if msg.startswith('column') and msg.endswith('not unique'):
+                raise DuplicateEntryError(msg)
+            else:
+                raise IntegrityError(msg)
+        except self.module.InternalError, e:
+            raise InternalError(ErrorMessage(e))
+        except self.module.ProgrammingError, e:
+            raise ProgrammingError(ErrorMessage(e))
+        except self.module.DataError, e:
+            raise DataError(ErrorMessage(e))
+        except self.module.NotSupportedError, e:
+            raise NotSupportedError(ErrorMessage(e))
+        except self.module.DatabaseError, e:
+            raise DatabaseError(ErrorMessage(e))
+        except self.module.InterfaceError, e:
+            raise InterfaceError(ErrorMessage(e))
+        except self.module.Warning, e:
+            raise Warning(ErrorMessage(e))
+        except self.module.Error, e:
+            raise Error(ErrorMessage(e))
+
     def _queryInsertID(self, conn, soInstance, id, names, values):
         table = soInstance.sqlmeta.table
         idName = soInstance.sqlmeta.idName
@@ -164,7 +203,7 @@
         q = self._insertSQL(table, names, values)
         if self.debug:
             self.printDebug(conn, q, 'QueryIns')
-        c.execute(q)
+        self._executeRetry(conn, c, q)
         # lastrowid is a DB-API extension from "PEP 0249":
         if id is None:
             id = int(c.lastrowid)
Index: mysql/mysqlconnection.py
===================================================================
--- mysql/mysqlconnection.py    (revision 1827)
+++ mysql/mysqlconnection.py    (working copy)
@@ -1,7 +1,16 @@
 from sqlobject.dbconnection import DBAPI
+from sqlobject.dberrors import *
 from sqlobject import col
 MySQLdb = None
 
+class ErrorMessage(str):
+    def __new__(cls, e):
+        obj = str.__new__(cls, e[1])
+        obj.code = int(e[0])
+        obj.module = e.__module__
+        obj.exception = e.__class__.__name__
+        return obj
+
 class MySQLConnection(DBAPI):
 
     supportTransactions = False
@@ -44,9 +53,9 @@
             conn = self.module.connect(host=self.host, port=self.port,
                 db=self.db, user=self.user, passwd=self.password, **self.kw)
         except self.module.OperationalError, e:
-            raise self.module.OperationalError(
+            raise OperationalError(
                 "%s; used connection string: host=%s, port=%s, db=%s, user=%s, 
pwd=%s" % (
-                e, self.host, self.port, self.db, self.user, self.password)
+                e, self.host, self.port, self.db, self.user, self.password)    
            
             )
 
         if hasattr(conn, 'autocommit'):
@@ -61,15 +70,39 @@
             conn.autocommit(auto)
 
     def _executeRetry(self, conn, cursor, query):
+        if self.debug:
+            self.printDebug(conn, query, 'QueryR')
         while 1:
             try:
                 return cursor.execute(query)
             except MySQLdb.OperationalError, e:
-                if e.args[0] == 2013: # SERVER_LOST error
+                if e.args[0] in (2006, 20013): # SERVER_GONE or SERVER_LOST 
error
                     if self.debug:
                         self.printDebug(conn, str(e), 'ERROR')
                 else:
-                    raise
+                    raise OperationalError(ErrorMessage(e))
+            except MySQLdb.IntegrityError, e:
+                msg = ErrorMessage(e)
+                if e.args[0] == 1062:
+                    raise DuplicateEntryError(msg)
+                else:
+                    raise IntegrityError(msg)
+            except MySQLdb.InternalError, e:
+                raise InternalError(ErrorMessage(e))
+            except MySQLdb.ProgrammingError, e:
+                raise ProgrammingError(ErrorMessage(e))
+            except MySQLdb.DataError, e:
+                raise DataError(ErrorMessage(e))
+            except MySQLdb.NotSupportedError, e:
+                raise NotSupportedError(ErrorMessage(e))
+            except MySQLdb.DatabaseError, e:
+                raise DatabaseError(ErrorMessage(e))
+            except MySQLdb.InterfaceError, e:
+                raise InterfaceError(ErrorMessage(e))
+            except MySQLdb.Warning, e:
+                raise Warning(ErrorMessage(e))
+            except MySQLdb.Error, e:
+                raise Error(ErrorMessage(e))
 
     def _queryInsertID(self, conn, soInstance, id, names, values):
         table = soInstance.sqlmeta.table
@@ -122,8 +155,8 @@
             # which is not always True (for an embedded application, e.g.)
             self.query('DESCRIBE %s' % (tableName))
             return True
-        except MySQLdb.ProgrammingError, e:
-            if e.args[0] == 1146: # ER_NO_SUCH_TABLE
+        except ProgrammingError, e:
+            if e.code == 1146: # ER_NO_SUCH_TABLE
                 return False
             raise
 
Index: dberrors.py
===================================================================
--- dberrors.py (revision 0)
+++ dberrors.py (revision 0)
@@ -0,0 +1,21 @@
+"""dberrors: database exception classes for SQLObject.
+
+   These classes are dictated by the DB API v2.0:
+   
+   http://www.python.org/topics/database/DatabaseAPI-2.0.html
+"""
+
+class Error(StandardError): pass
+class Warning(StandardError): pass
+
+class InterfaceError(Error): pass
+class DatabaseError(Error): pass
+
+class InternalError(DatabaseError): pass
+class OperationalError(DatabaseError): pass
+class ProgrammingError(DatabaseError): pass
+class IntegrityError(DatabaseError): pass
+class DataError(DatabaseError): pass
+class NotSupportedError(DatabaseError): pass
+
+class DuplicateEntryError(IntegrityError): pass
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
sqlobject-discuss mailing list
sqlobject-discuss@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sqlobject-discuss

Reply via email to