It seems that everyday we have a new reason to move away from using
SQLObject as an object-relational mapper in
ECP. The latest issue with
SQLObject has been rather challenging to work-around. The problem comes from the
ErrorMessage class defined in
SQLObject. Here is what the class looks like.
#SQLObject ErrorMessage class.
class ErrorMessage(str):
def __new__(cls, e, append_msg=''):
obj = str.__new__(cls, e[1] + append_msg)
obj.code = int(e[0])
obj.module = e.__module__
obj.exception = e.__class__.__name__
return obj
The problem we are experiencing with
ECP is the fact that this class always raises an
IndexError. The reason being, the
ErrorMessage.__new__() method makes the assumption that the
0 and
1 indices will always be available in the
e parameter. The
e parameter is supposed to be an instance of
Exception.
The question that now arises is how do we handle this? In this case, we have exceptions being raised by other exceptions. The
ErrorMessage class could simply be fixed by adding exception handling for
IndexError exceptions. However, now that the error is fixed, how do we ship this fix along with our application?
ECP will currently install
SQLObject from
pypi. One solution would be to build our own
SQLObject package and point the
ECP setup to a custom repository that contains this patched-version. One problem I find with this solution is that it could potentially introduce a myriad of other deployment problems.
Another solution is to perform the patch inside of
ECP. In this scenario, we don't actually patch the
SQLObject package. The
SQLObject package would remain as is on the system so that other
Python applications using
SQLObject wouldn't experience any side-effects as a result of
ECP providing a different
SQLObject. And this is the approach we are taking. Once
ECP has started up, we import the
mysqlconnection module and replace
ErrorMessage entirely. Here is how it is done.
#ECP approach to patching SQLObject.
from sqlobject.mysql import mysqlconnection
class NewErrorMessage(str):
def __new__(cls, e):
if not isinstance(e, Exception):
e = e.args[0]
else:
try:
dummy = e[1]
except IndexError:
e = e.args[0]
obj = str.__new__(cls, e[1])
obj.code = int(e[0])
obj.module = e.__module__
obj.exception = e.__class__.__name__
return obj
mysqlconnection.ErrorMessage = NewErrorMessage
What is shown here is a new implementation of the
ErrorMessage class;
NewErrorMessage. The interface of the original class is kept in tact. What has changed is the exception handling inside the exception. We first test if the
e parameter is in fact an
Exception instance. Next, we test for
IndexError exceptions and rebuild the
e parameter if necessary. The method then continues on as in the original implementation. Finally, we then replace the
ErrorMessage class with
NewErrorMessage. This all happens in
enomalism2d so that the new error message class is available right away, before it is actually needed.
As an afterthought, I'm wondering what led
SQLObject to this issue to begin with. That is, how can a class so tightly associated with exception handling be the culprit for bigger problems such as this one? Is it that this class is taking on too many responsibilities and thus adding to the risk of raising unforeseen exceptions itself? I wouldn't think so. The
ErrorMessage.__new__() method isn't exactly overwhelmed with code. Besides, the exceptions defined in
ECP do a fair amount of work when instantiated (including interacting with
SQLObject). When the
ECP exceptions are raised, they never raise inadvertent exceptions.
Perhaps special care needs to be taken when defining exceptions that do any work. If nothing else,
SQLObject provides us with a lesson learned. As developers, we need to be absolutely certain that any given exception we have defined ourselves can be raised under any circumstances. They cannot fail. It would obviously be nice of no code failed at all throughout an entire application. That is obviously not a reality though. The code will fail at some point and having stable exception to deal with will make your code one step closer to being fail safe.