Hello,
I've read the "Handling exceptions" topic on this mailing list
(http://www.mail-archive.com/sqlobject-discuss%40lists.sourceforge.net/msg00975.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. However I have no great
experience in other
database APIs (Postgres, Sybase, etc), so we should see if they also
respect the PEP249 specification,
or if there's a way to map their exceptions according to it.
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.
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