python: provide more exception classes
authorJustus Winter <4winter@informatik.uni-hamburg.de>
Mon, 26 Sep 2011 01:05:35 +0000 (03:05 +0200)
committerSebastian Spaeth <Sebastian@SSpaeth.de>
Fri, 30 Sep 2011 11:58:03 +0000 (13:58 +0200)
To make the exception handling more effective in code using the
python bindings it is necessary to differentiate between the
different kind of failures.

Add an exception class for each status code and add a decode
classmethod to the NotmuchError class that acts as a factory.

Import the new classes in __init__.py so they can be easily
imported by anyone.

Patch modifed by Sebastian Spaeth.

Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de>
Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de>
bindings/python/notmuch/__init__.py
bindings/python/notmuch/database.py
bindings/python/notmuch/globals.py

index a7b558fa746a01b9716689ffa375c44d5640af87..36e5fc7a0754060a45bbb3bdcfb993dfbea7b4e4 100644 (file)
@@ -50,13 +50,28 @@ for more details.
 You should have received a copy of the GNU General Public License
 along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
 
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
+Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
 from notmuch.database import Database, Query
 from notmuch.message import Messages, Message
 from notmuch.thread import Threads, Thread
 from notmuch.tag import Tags
-from notmuch.globals import nmlib, STATUS, NotmuchError
+from notmuch.globals import (
+    nmlib,
+    STATUS,
+    NotmuchError,
+    OutOfMemoryError,
+    ReadOnlyDatabaseError,
+    XapianError,
+    FileError,
+    FileNotEmailError,
+    DuplicateMessageIdError,
+    NullPointerError,
+    TagTooLongError,
+    UnbalancedFreezeThawError,
+    UnbalancedAtomicError,
+    NotInitializedError
+)
 from notmuch.version import __VERSION__
 __LICENSE__ = "GPL v3+"
 __AUTHOR__ = 'Sebastian Spaeth <Sebastian@SSpaeth.de>'
index 1e6d33753ec725c123a55ed893c31675353a4368..644e2e5fa68a2615f0084158fcb52de68683adc6 100644 (file)
@@ -112,7 +112,7 @@ class Database(object):
     def _assert_db_is_initialized(self):
         """Raises a NotmuchError in case self._db is still None"""
         if self._db is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
     def create(self, path):
         """Creates a new notmuch database
@@ -157,8 +157,7 @@ class Database(object):
         res = Database._open(_str(path), mode)
 
         if res is None:
-            raise NotmuchError(
-                message="Could not open the specified database")
+            raise NotmuchError(message="Could not open the specified database")
         self._db = res
 
     def get_path(self):
@@ -232,7 +231,7 @@ class Database(object):
         self._assert_db_is_initialized()
         status = nmlib.notmuch_database_begin_atomic(self._db)
         if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
+            raise NotmuchError.get_subclass_exc(status)
         return status
 
     def end_atomic(self):
@@ -254,7 +253,7 @@ class Database(object):
         self._assert_db_is_initialized()
         status = nmlib.notmuch_database_end_atomic(self._db)
         if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
+            raise NotmuchError.get_subclass_exc(status)
         return status
 
     def get_directory(self, path):
@@ -285,7 +284,7 @@ class Database(object):
             # we got an absolute path
             if not path.startswith(self.get_path()):
                 # but its initial components are not equal to the db path
-                raise NotmuchError(STATUS.FILE_ERROR,
+                raise NotmuchError.get_subclass_exc(STATUS.FILE_ERROR,
                                    message="Database().get_directory() called "
                                            "with a wrong absolute path.")
             abs_dirpath = path
@@ -356,7 +355,7 @@ class Database(object):
                                                   byref(msg_p))
 
         if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
-            raise NotmuchError(status)
+            raise NotmuchError.get_subclass_exc(status)
 
         #construct Message() and return
         msg = Message(msg_p, self)
@@ -450,7 +449,7 @@ class Database(object):
         self._assert_db_is_initialized()
         tags_p = Database._get_all_tags(self._db)
         if tags_p == None:
-            raise NotmuchError(STATUS.NULL_POINTER)
+            raise NotmuchError.get_subclass_exc(STATUS.NULL_POINTER)
         return Tags(tags_p, self)
 
     def create_query(self, querystring):
@@ -574,13 +573,13 @@ class Query(object):
                         (too little memory)
         """
         if db.db_p is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
         # create reference to parent db to keep it alive
         self._db = db
         # create query, return None if too little mem available
         query_p = Query._create(db.db_p, _str(querystr))
         if query_p is None:
-            raise NotmuchError(STATUS.NULL_POINTER)
+            raise NotmuchError.get_subclass_exc(STATUS.NULL_POINTER)
         self._query = query_p
 
     def set_sort(self, sort):
@@ -594,7 +593,7 @@ class Query(object):
                     been initialized.
         """
         if self._query is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
         self.sort = sort
         nmlib.notmuch_query_set_sort(self._query, sort)
@@ -620,7 +619,7 @@ class Query(object):
                       * :attr:`STATUS`.NULL_POINTER if search_threads failed
         """
         if self._query is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
         threads_p = Query._search_threads(self._query)
 
@@ -643,12 +642,12 @@ class Query(object):
                       * :attr:`STATUS`.NULL_POINTER if search_messages failed
         """
         if self._query is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
         msgs_p = Query._search_messages(self._query)
 
         if msgs_p is None:
-            raise NotmuchError(STATUS.NULL_POINTER)
+            raise NotmuchError.get_subclass_exc(STATUS.NULL_POINTER)
 
         return Messages(msgs_p, self)
 
@@ -668,7 +667,7 @@ class Query(object):
                       * :attr:`STATUS`.NOT_INITIALIZED if query is not inited
         """
         if self._query is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
         return Query._count_messages(self._query)
 
@@ -711,7 +710,7 @@ class Directory(object):
     def _assert_dir_is_initialized(self):
         """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None"""
         if self._dir_p is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
     def __init__(self, path, dir_p, parent):
         """
@@ -771,7 +770,7 @@ class Directory(object):
         if status == STATUS.SUCCESS:
             return
         #fail with Exception otherwise
-        raise NotmuchError(status)
+        raise NotmuchError.get_subclass_exc(status)
 
     def get_mtime(self):
         """Gets the mtime value of this directory in the database
@@ -857,7 +856,7 @@ class Filenames(object):
 
     def next(self):
         if self._files_p is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
         if not nmlib.notmuch_filenames_valid(self._files_p):
             self._files_p = None
@@ -880,7 +879,7 @@ class Filenames(object):
                      for file in files: print file
         """
         if self._files_p is None:
-            raise NotmuchError(STATUS.NOT_INITIALIZED)
+            raise NotmuchError.get_subclass_exc(STATUS.NOT_INITIALIZED)
 
         i = 0
         while nmlib.notmuch_filenames_valid(self._files_p):
index 097ab96800d1dcaa3336f037605be61561b1d0fd..5fca3d9bd7fc1bc4c02e5b8ef2bc2d26dee04822 100644 (file)
@@ -89,10 +89,32 @@ Invoke the class method `notmuch.STATUS.status2str` with a status value as
 argument to receive a human readable string"""
 STATUS.__name__ = 'STATUS'
 
-
 class NotmuchError(Exception):
-    def __init__(self, status=None, message=None):
-        """Is initiated with a (notmuch.STATUS[,message=None])"""
+    """Is initiated with a (notmuch.STATUS[, message=None]). It will not
+    return an instance of the class NotmuchError, but a derived instance
+    of a more specific Error Message, e.g. OutOfMemoryError. Each status
+    but SUCCESS has a corresponding subclassed Exception."""
+
+    @classmethod
+    def get_subclass_exc(cls, status, message=None):
+        """Returns a fine grained Exception() type,detailing the error status"""
+        subclasses = {
+            STATUS.OUT_OF_MEMORY: OutOfMemoryError,
+            STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
+            STATUS.XAPIAN_EXCEPTION: XapianError,
+            STATUS.FILE_ERROR: FileError,
+            STATUS.FILE_NOT_EMAIL: FileNotEmailError,
+            STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
+            STATUS.NULL_POINTER: NullPointerError,
+            STATUS.TAG_TOO_LONG: TagTooLongError,
+            STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
+            STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
+            STATUS.NOT_INITIALIZED: NotInitializedError
+        }
+        assert 0 < status <= len(subclasses)
+        return subclasses[status](status, message)
+
+    def __init__(self, status, message=None):
         self.status = status
         self.message = message
 
@@ -104,6 +126,32 @@ class NotmuchError(Exception):
         else:
             return 'Unknown error'
 
+# List of Subclassed exceptions that correspond to STATUS values and are
+# subclasses of NotmuchError:
+class OutOfMemoryError(NotmuchError):
+    pass
+class ReadOnlyDatabaseError(NotmuchError):
+    pass
+class XapianError(NotmuchError):
+    pass
+class FileError(NotmuchError):
+    pass
+class FileNotEmailError(NotmuchError):
+    pass
+class DuplicateMessageIdError(NotmuchError):
+    pass
+class NullPointerError(NotmuchError):
+    pass
+class TagTooLongError(NotmuchError):
+    pass
+class UnbalancedFreezeThawError(NotmuchError):
+    pass
+class UnbalancedAtomicError(NotmuchError):
+    pass
+class NotInitializedError(NotmuchError):
+    pass
+
+
 def _str(value):
     """Ensure a nicely utf-8 encoded string to pass to libnotmuch